3

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.

kevboh
  • 5,207
  • 5
  • 38
  • 54
  • 1
    You should check if your initial image is just being interpreted according to its orientation or is physically rotated. Check if image.imageSize and CGImageGetHeight and CGImageGetWidth return the same values. Also check any metadata. BTW you're using kCGImageSourceCreateThumbnailWithTransform which might, or might not rotate thumbnail. Do checks above and you'll hopefully find out what's going on. – TheBlack May 23 '11 at 17:45
  • image.size is 1936x2592, but CGImageGetWidth is 2592 and CGImageGetHeight is 1936. Looks like it's physically rotated. Do you know of a way to rotate the image without loading the entire thing into an image context? – kevboh May 23 '11 at 18:40
  • @kevboh Does the UIImageView displays full res image in correct orientation? If yes and you use things like cgcontext or catiledlayer the answer is to handle the rotation yourself at point of drawing as nothing is wrong with image itself and rotating image otherwise doesn't makes sense. What about thumbnail? Not sure about kCGImageSourceCreateThumbnailWithTransform and if rotation data is passed to image. So, according to comments from my code, UIImage always returns the size that matches correct orientation, while CGImageRefGetxxxx always returns orientation UIImageOrientationUp. – TheBlack May 23 '11 at 22:01
  • Try this: UIImage *thumbnail = [UIImage imageWithCGImage:imgRef scale:1.0f orientation:UIImageOrientationUp]; for thumbnail or self.image.imageOrientation instead of UIImageOrientationUp, depending on if kCGImageSourceCreateThumbnailWithTransform is true or false, you decide. – TheBlack May 23 '11 at 22:06
  • To summarize:the issue is confusing and requires care to handle properly. UIImage Orientation is generally pulled from image metadata and top level stuff like UIImageView handles rotation automatically. For anything lower, Quartz alike, developer has to handle orientation himself. In that case, The best way is to just go along with Quartz manipulation and then instantiate UIImage with original UIImage.imageOrientation. Specific stuff like catiledlayer requires doing rotation manually. RAW photo handling support was (is?) flaky for some makes, the most troublesome being Nikon. – TheBlack May 23 '11 at 22:30
  • I'm never actually displaying the full res image, although I suppose I could for testing. Typically my uploaded (full res) image matches the thumbnail in orientation. I've tried imageWithCGImage: before but I might've been doing it wrong, I'll try again with different orientation permutations. Thanks for the help, I'll post back if I figure anything out. Don't want to waste too much time trying to optimize this thing. – kevboh May 24 '11 at 13:50
  • 1
    More suggestions: http://stackoverflow.com/questions/4565507/ – TheBlack May 24 '11 at 15:28
  • Thanks. I guess I'm pretty much limited to doing this entirely in memory or doing it on the server, which is an interesting take. I think I'll do it in memory for now and try to work the server side in later. If you want to repost an amalgamation of your comments as an answer, I'd be happy to mark it, since you've helped so much. Thanks again! – kevboh May 25 '11 at 13:17

1 Answers1

0

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.

kevboh
  • 5,207
  • 5
  • 38
  • 54