12

Basic Task: update the EXIF orientation property for the metaData asociated with a UIImage. My problem is that I don't know where the orientation property is in all the EXIF info.


Convoluted Background: I am changing the orientation of the image returned by imagePickerController:didFinishPickingMediaWithInfo: so I am thinking that I also need to update the metaData before saving the image with writeImageToSavedPhotosAlbum:(CGImageRef)imageRef metadata:(NSDictionary *)metadata.

In other words, unless I change it, the metaData will contain the old/initial orientation and thus be wrong. The reason I am changing the orientation is because it keeps tripping me up when I run the Core Image face detection routine. Taking a photo with the iPhone (device) in Portrait mode using the front camera, the orientation is UIImageOrientationRight (3). If I rewrite the image so the orientation is UIImageOrientationUp(0), I get good face detection results. For reference, the routine to rewrite the image is below.

The whole camera orientation thing I find very confusing and I seem to be digging myself deeper into a code hole with all of this. I have looked at the posts (here, here and here. And according to this post (https://stackoverflow.com/a/3781192/840992):

"The camera is actually landscape native, so you get up or down when you take a picture in landscape and left or right when you take a picture in portrait (depending on how you hold the device)."

...which is totally confusing. If the above is true, I would think I should be getting an orientation of UIImageOrientationLeftMirrored or UIImageOrientationRightMirrored with the front camera. And none of this would explain why the CIDetector fails the virgin image returned by the picker.

I am approaching this ass-backwards but I can't seem to get oriented...


-(UIImage *)normalizedImage:(UIImage *) thisImage
{
    if (thisImage.imageOrientation == UIImageOrientationUp) return thisImage;

    UIGraphicsBeginImageContextWithOptions(thisImage.size, NO, thisImage.scale);
    [thisImage drawInRect:(CGRect){0, 0, thisImage.size}];
    UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return normalizedImage;
}
Andrew Herbert
  • 1,269
  • 1
  • 9
  • 5
spring
  • 18,009
  • 15
  • 80
  • 160
  • "If I rewrite the image so the orientation is UIImageOrientationRight(0)," I think you're referring to `UIImageOrientationUp`. – KudoCC May 17 '16 at 08:36

2 Answers2

23

Take a look at my answer here:
Force UIImagePickerController to take photo in portrait orientation/dimensions iOS

and associated project on github (you won't need to run the project, just look at the readme).

It's more concerned with reading rather than writing metadata - but it includes a few notes on Apple's imageOrientation and the corresponding orientation 'Exif' metadata.

This might be worth a read also
Captured photo automatically rotated during upload in IOS 6.0 or iPhone

There are two different constant numbering conventions in play to indicate image orientation.

  1. kCGImagePropertyOrientation constants as used in TIFF/IPTC image metadata tags
  2. UIImageOrientation constants as used by UIImage imageOrientation property.

iPhones native camera orientation is landscape left (with home button to the right). Native pixel dimensions always reflect this, rotation flags are used to orient the image correctly with the display orientation.

Apple            UIImage.imageOrientation     TIFF/IPTC kCGImagePropertyOrientation

iPhone native     UIImageOrientationUp    = 0  =  Landscape left  = 1  
rotate 180deg     UIImageOrientationDown  = 1  =  Landscape right = 3  
rotate 90CCW      UIImageOrientationLeft  = 2  =  Portrait  down  = 8  
rotate 90CW       UIImageOrientationRight = 3  =  Portrait  up    = 6  

UIImageOrientation 4-7 map to kCGImagePropertyOrientation 2,4,5,7 - these are the mirrored counterparts.

UIImage derives it's imagerOrientation property from the underlying kCGImagePropertyOrientation flags - that's why it is a read-only property. This means that as long as you get the metadata flags right, the imageOrientation will follow correctly. But if you are reading the numbers in order to apply a transform, you need to be aware which numbers you are looking at.

Community
  • 1
  • 1
foundry
  • 31,615
  • 9
  • 90
  • 125
5

A few gleanings from my world o' pain in looking into this:

Background: Core Image face detection was failing and it seemed to be related to using featuresInImage:options: and using the UIImage.imageOrientation property as an argument. With an image adjusted to have no rotation and not mirrored, detection worked fine but when passing in an image directly from the camera detection failed.

Well...UIImage.imageOrientation is DIFFERENT than the actual orientation of the image.

In other words...

UIImage* tmpImage = [self.imageInfo objectForKey:UIImagePickerControllerOriginalImage];
printf("tmpImage.imageOrientation: %d\n", tmpImage.imageOrientation);

Reports a value of 3 or UIImageOrientationRight whereas using the metaData returned by the UIImagePickerControllerDelegate method...

   NSMutableDictionary* metaData = [[tmpInfo objectForKey:@"UIImagePickerControllerMediaMetadata"] mutableCopy];
   printf(" metaData orientation    %d\n", [[metaData objectForKey:@"Orientation"] integerValue]);

Reports a value of 6 or UIImageOrientationLeftMirrored.

I suppose it seems obvious now that UIImage.imageOrientation is a display orientation which appears to determined by source image orientation and device rotation. (I may be wrong here) Since the display orientation is different than the actual image data, using that will cause the CIDetector to fail. Ugh.

I'm sure all of that serves very good and important purposes for liquid GUIs etc. but it is too much to deal with for me since all the CIDetector coordinates will also be in the original image orientation making CALayer drawing sick making. So, for posterity here is how to change the Orientation property of the metaData AND the image contained therein. This metaData can then be used to save the image to the cameraRoll.

Solution

// NORMALIZE IMAGE
UIImage* tmpImage = [self normalizedImage:[self.imageInfo objectForKey:UIImagePickerControllerOriginalImage]];
NSMutableDictionary * tmpInfo =[self.imageInfo mutableCopy];
NSMutableDictionary* metaData = [[tmpInfo objectForKey:@"UIImagePickerControllerMediaMetadata"] mutableCopy];

[metaData setObject:[NSNumber numberWithInt:0] forKey:@"Orientation"];
[tmpInfo setObject:tmpImage forKey:@"UIImagePickerControllerOriginalImage"];
[tmpInfo setObject:metaData forKey:@"UIImagePickerControllerMediaMetadata"];

self.imageInfo = tmpInfo;
spring
  • 18,009
  • 15
  • 80
  • 160
  • "Well...UIImage.imageOrientation is DIFFERENT than the actual orientation of the image." - not quite.. `UIImageOrientation` constants (used by the `UIImage` `imageOrientation` property and `kCGImagePropertyOrientation` constants used by TIFF/IPTC metatags both signify the same thing, they just use different numbering conventions. You have to translate between the two. But as imageOrientation is _derived_ from `kCGImagePropertyOrientation`, as long as you get the latter right the former will be correct when a UIImage reads in its image data. – foundry Jan 28 '13 at 02:41