rotate CGImage on disk using CGImageSource/CGImageDestination? - objective-c

I'm working on an application that needs to take a picture using UIImagePickerController, display a thumbnail of it in the app, and also submit that picture to a server using ASIFormDataRequest (from ASIHTTPRequest).
I want to use setFile:withFileName:andContentType:forKey: from ASIFormDataRequest since in my experience it's faster than trying to submit an image using UIImageJPEGRepresentation and submitting raw NSData. To that end I'm using CGImageDestination and creating an image destination with a url, saving that image to disk, and then uploading that file on disk.
In order to create the thumbnail I'm using CGImageSourceCreateThumbnailAtIndex (see docs) and creating an image source with the path of the file I just saved.
My problem is that no matter what options I pass into the image destination or the thumbnail creation call, my thumbnail always comes out rotated 90 degrees counterclockwise. The uploaded image is also rotated. I've tried explicitly setting the orientation in the options of the image using CGImageDestinationSetProperties but it doesn't seem to take. The only solution I've found is to rotate the image in memory, but I really want to avoid that since doing so doubles the time it takes for the thumbnail+saving operation to finish. Ideally I'd like to be able to rotate the image on disk.
Am I missing something in how I'm using CGImageDestination or CGImageSource? I'll post some code below.
Saving the image:
NSURL *filePath = [NSURL fileURLWithPath:self.imagePath];
CGImageRef imageRef = [self.image CGImage];
CGImageDestinationRef ref = CGImageDestinationCreateWithURL((CFURLRef)filePath, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImage(ref, imageRef, NULL);
NSDictionary *props = [[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:1.0], kCGImageDestinationLossyCompressionQuality,
nil] retain];
//Note that setting kCGImagePropertyOrientation didn't work here for me
CGImageDestinationSetProperties(ref, (CFDictionaryRef) props);
CGImageDestinationFinalize(ref);
CFRelease(ref);
Generating the thumbnail
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)filePath, NULL);
if (!imageSource)
return;
CFDictionaryRef options = (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform,
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageIfAbsent,
(id)[NSNumber numberWithFloat:THUMBNAIL_SIDE_LENGTH], (id)kCGImageSourceThumbnailMaxPixelSize, nil];
CGImageRef imgRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options);
UIImage *thumb = [UIImage imageWithCGImage:imgRef];
CGImageRelease(imgRef);
CFRelease(imageSource);
And then to upload the image I just use
[request setFile:path withFileName:fileName andContentType:contentType forKey:#"photo"];
where path is the path to the file saved with the code above.

As far as I know and after trying lots of different things, this cannot be done with current public APIs and has to be done in memory.

Related

Access dark versions of .heic image files with Objective-C

On MacOS, .heic image files can contain one light and one dark version. This can be seen for system wallpapers using open: for example
open /System/Library/Desktop\ Pictures/Dome.heic
The Preview app displays both versions. My goal is to be able to access them using Objective-C, as I would like to save both light and dark versions to .jpg format.
Following snippet succeeds in extracting the light version and saving it to disk:
NSString* wallpaper_path = /* insert source path here */;
NSString* converted_path_str = /* insert destination path here */;
NSImage* image = [[[NSImage alloc] initWithContentsOfFile:wallpaper_path] autorelease];
NSData* data = [image TIFFRepresentation];
NSBitmapImageRep* rep = [NSBitmapImageRep imageRepWithData:data];
NSDictionary* properties =[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
data = [rep representationUsingType:NSJPEGFileType properties:properties];
[data writeToFile:converted_path_str atomically:NO];
However I cannot find how to access the dark version using NSImage, NSImageRep or NSBitmapImageRep.
As Preview succeeds in doing it, I think it's possible though. I just don't know how to do.

How can I extract image information dumped in NSData

I have as an input the dump of an image in an NSData object. Now, I want to extract relevant information of the image from this object like number of pixels, no. of bits per pixel, etc.
Can anyone tell me how to extract this info from the NSData object dump?
P.S.: I have gone through this documentation of the NSData class, but could not isolate out the relevant methods.
So the easiest way is to actually build the UIImage object from the NSData then extract the info from the UIImage then.
UIImage* image = [UIImage imageWithData:yourData];
NSLog(#"Image is %dx%d",image.size.width, image.size.height);
If you are only interested in the properties of the image but don't want to actually build its representation and only get the properties, take a look at CGImageSource
#import <ImageIO/ImageIO.h>
CGImageSourceRef imgSrc = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t nbImages = CGImageSourceGetCount(imgSrc);
for(size_t idx=0; idx<nbImages; ++idx)
{
NSDictionary* props = (__bridge NSDictionary*)CGImageSourceCopyPropertiesAtIndex(imgSrc, idx, NULL);
NSLog(#"properties for image %lu in imageSource: %#", idx, props);
}
CFRelease(imgSrc);
[EDIT] For this to work, obviously add the ImageIO.framework to your "Link Binary With Libraries"
Convert the data to UIImage, then take a look at this post.
UIImage *image = [UIImage imageWithData:data];

How do I create a valid CGImageSourceRef from an ALAssetRepresentation?

I'm trying to use CGImageSourceCreateThumbnailAtIndex to efficiently create a resized version of an image. I have some existing code that does this with images from disk, and now I'm trying to use an image that comes from ALAssetsLibrary.
Here's my code:
ALAsset *asset;
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
CGImageSourceRef sourceRef = CGImageSourceCreateWithDataProvider(provider, NULL);
NSDictionary *resizeOptions = #{
kCGImageSourceCreateThumbnailWithTransform : #YES,
kCGImageSourceCreateThumbnailFromImageAlways : #YES,
kCGImageSourceThumbnailMaxPixelSize : #(2100)
};
CGImageRef resizedImage = CGImageSourceCreateThumbnailAtIndex(source, 0, resizeOptions);
The problem is that resizedImage is null, and CGImageSourceGetCount(sourceRef) returns 0. The data provider does have quite a bit of data in it, though, and the data does appear to be valid image data. The ALAsset comes from an iPhone 4S camera roll.
What am I missing? Why does CGImageSourceCreateWithDataProvider() create an image source with 0 images?
CGImageSource is for deserializing serialized images, such as JPEGs, PNGs, and whatnot.
CGImageGetDataProvider returns (the provider of) the raw pixel data of the image. It does not return serialized bytes in some external format. CGImageSource has no way to know what pixel format (color space, bits-per-component, alpha layout, etc.) any given raw pixel data is in.
You could try getting the URL of the asset rep and giving that to CGImageSourceCreateWithURL. If that doesn't work (e.g., not a file URL), you'll have to run the image through a CGImageDestination and create a CGImageSource with wherever you put the output.
(The one other thing to try would be to see whether the rep's filename is actually a full path, the way Cocoa often misuses the term. But you probably shouldn't count on that.)
One thing you might try is the asset rep's CGImageWithOptions: method.
The documentation claims that:
This method returns the biggest, best representation available, unadjusted in any way.
But it says that about fullResolutionImage, too, and I'm not sure why this class would have both methods if they both do the same thing. I wonder if it's a copy-and-paste error.
Try CGImageWithOptions: with a bunch of thumbnail-creating options and see what happens.
Option #3 would be the rep's fullScreenImage. Depending on what sort of “thumbnail” you need, it may be cheaper and/or simpler to just use this, which will be no bigger than (approximately) the size of the device's screen.
This can also help...
ALAssetRepresentation* rep = [asset defaultRepresentation];
NSDictionary* options = [[NSDictionary alloc] initWithObjectsAndKeys:
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform,
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageAlways,
(id)[NSNumber numberWithDouble:400], (id)kCGImageSourceThumbnailMaxPixelSize,
nil];
CGImageRef image = [rep CGImageWithOptions:options];

iOS: Load PNG from disk, add PixelPerMeter properties to it, and save it to the Photo Album

Apparently with CGImages you can add kCGImagePropertyPNGXPixelsPerMeter and kCGImagePropertyPNGYPixelsPerMeter properties to the image and they will get added to the pHYs chunk of the png. But UIImageWriteToSavedPhotosAlbum doesn't directly accept a CGImage.
I've been trying to load the image as a UIImage, then create a CGImage from that, add the properties, and then create a new UIImage from that data and saving that to the photo album.
An image is getting written to the photo album, but without the PPM settings. I can verify this in the preview app on MacOS, or using ImageMagick's identify.
ObjC isn't my domain, so here's what I've cobbled together from similar stackoverflow questions.
UIImage *uiImage = [UIImage imageWithContentsOfFile:path];
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:5905], (NSString *)kCGImagePropertyPNGXPixelsPerMeter, /* this doesn't work */
[NSNumber numberWithInt:5905], (NSString *)kCGImagePropertyPNGYPixelsPerMeter, /* this doesn't work */
#"This works when saved to disk.", (NSString *)kCGImagePropertyPNGDescription,
nil],(NSString*) kCGImagePropertyPNGDictionary,
nil];
NSMutableData* imageData = [NSMutableData data];
CGImageDestinationRef imageDest = CGImageDestinationCreateWithData((CFMutableDataRef) imageData, kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(imageDest, uiImage.CGImage, (CFDictionaryRef) properties);
CGImageDestinationSetProperties(imageDest, (CFDictionaryRef) properties); /* I'm setting the properties twice because I was going crazy */
CGImageDestinationFinalize(imageDest);
UIImageWriteToSavedPhotosAlbum( [UIImage imageWithData:imageData], nil, NULL, NULL );
// This is a test to see if the data is getting stored at all.
CGImageDestinationRef diskDest = CGImageDestinationCreateWithURL(
(CFURLRef)[NSURL fileURLWithPath:[NSString stringWithFormat:#"%#.dpi.png",path]], kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(diskDest, uiImage.CGImage, (CFDictionaryRef) properties);
CGImageDestinationSetProperties(diskDest, (CFDictionaryRef) properties);
CGImageDestinationFinalize(diskDest);
Updated: Revised and simplified the code a bit. Still saves image to photo album and disk, but the pixels per meter value is not saving. The description block gets saved to disk, but appears to get stripped when written to the photo album.
Update 2: By saving the file as a jpeg and adding TIFF X/Y Resolution tags to it I've been able to get the version saved to disk to store a PPI value other than 72. This information is still stripped off when saving to the photo album.
Comparing a photo taken via iOS Camera app, one taken via Instagram, and one saved by my code, I noticed that the Camera app version has a ton of TIFF/EXIF tags, and the Instagram and my one have the same limited set of tags. This leads me to the conclusion that iOS strips these tags intentionally. A definitive answer would help me sleep at night though.
The answer is don't use UIImageWriteToSavedPhotosAlbum.
There's an ALAssetsLibrary method
[writeImageDataToSavedPhotosAlbum: metadata: completionBlock:]
The metadata is a dictionary in the same format as the one passed to CGImageDestinationSetProperties. PNG and JFIF density settings may be broken, I'm using kCGImagePropertyTIFFXResolution.

TIFF image format for iphone application

I am working on iphone app and i need to save image into .tiff format.
It is possible to save image into png format using UIImagePNGRepresentation method and JPEG format using UIImageJPEGRepresentation. But i need to save signature captured by imageview into tiff format.
I unable to use NSImage class so that i can call TIFFRepresentation method.
How can i do it.Send me suggestion...
Thanks in advance...
I don't know what you mean by "captured by imageview". The means to save as TIFF wasn't introduced until iOS 4. Here is example code to save an arbitrary file as a TIFF; you can do this with any file-format data, no matter how you obtained it. You need to link to ImageIO framework and do #import <ImageIO/ImageIO.h>:
NSURL* url = [[NSBundle mainBundle] URLForResource:#"colson" withExtension:#"jpg"];
CGImageSourceRef src = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
NSFileManager* fm = [[NSFileManager alloc] init];
NSURL* suppurl = [fm URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES error:NULL];
NSURL* tiff = [suppurl URLByAppendingPathComponent:#"mytiff.tiff"];
CGImageDestinationRef dest = CGImageDestinationCreateWithURL((CFURLRef)tiff,
(CFStringRef)#"public.tiff", 1, NULL);
CGImageDestinationAddImageFromSource(dest, src, 0, NULL);
bool ok = CGImageDestinationFinalize(dest);
NSLog(#"result %i", ok); // 1, all went well
// and don't forget memory management, release source and destination, NSFileManager
[fm release]; CFRelease(src); CFRelease(dest);
Hmm, I don't develop for the iPhone, so can't tell you if there's a magic API way of doing this, but many years ago I had to create TIFF images manually.
The TIFF format is a bit whacky if you're used to just using a framework's CreateThisStuffAsAnImage() methods, but you can get the spec here and create the file yourself:
http://partners.adobe.com/public/developer/tiff/index.html#spec