EXIF pain, image orientations in iOS

I’ m working on a photo app with a lot of technical challenges to tackle, but I hate when you spend a lot of times on stupid things mostly due to the lack of documentation. EXIF orientation flag is one of them, so let’s try to explain it.

An image can contain additional information, those info can be stored inside the EXIF format specification.

EXIF Tags (or metadata) cover a lot of different aspects:

- timestamp information

- camera settings (such as ISO,white balance, orintation)

- a thumb image

- geographical coordination

- other descriptions and copyright info

Orientation flag is based on the rotation of the camera respect to the ground (and probably the scan directions of the image).

iOS cameras follow the same rules as new digital camera, so the EXIF orientation flag is a reality, the problem is that conflicts somehow with the UIKit defined enumeration for UIImage orientations.

typedef enum {
 UIImageOrientationUp =            0, // 0 deg rotation exif 1
 UIImageOrientationDown =          1, // 180 deg rotation exif 3
 UIImageOrientationLeft =          2, // 90 deg CCW exif 6
 UIImageOrientationRight =         3, // 90 deg CW exif 8
 UIImageOrientationUpMirrored =    4, // horizontal flip exif 2
 UIImageOrientationDownMirrored =  5, // horizontal flip exif 4
 UIImageOrientationLeftMirrored =  6, // vertical flip exif 5
 UIImageOrientationRightMirrored = 7, // vertical flip exif 7
} UIImageOrientation;

Let’s see how orientations behave during 3 different kinds of image acquisition.

We will start with UIImagePickerController. Doing some experiment shooting and rotating the device we obtain this table.

Using back camera:

DeviceOrientation UIImageOrientation EXIF
Portrait UIImageOrientationRight 6
LandRight UIImageOrientationUp 1(default)
LandLeft UIImageOrientationDown 3
PotraitUpsideDown UIImageOrientationLeft 8

With front camera:

DeviceOrientation UIImageOrientation EXIF
Portrait UIImageOrientationRight 6
LandRight UIImageOrientationDown 3
LandLeft UIImageOrientationUp 1
PotraitUpsideDown UIImageOrientationLeft 8

To obtain those data you just need to add those few lines of code inside the delgate method:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
 // ...
 UIImage * originalImage = (UIImage *) [info objectForKey:  UIImagePickerControllerOriginalImage];
 NSDictionary *i mageMetadata = [info objectForKey: UIImagePickerControllerMediaMetadata];
 NSLog(@"Metadata Exif Orientation: %@   UIImageOrientation %d", imageMetadata[(NSString*)kCGImagePropertyOrientation],   originalImage.imageOrientation);
 //...
}

Using AVStillImageOutput things change. Avoiding the orientation correction that we could apply using -videoOutputOrientation on AVCaptureConnection we will obtain always the same number, the exif orientation number 6

 [[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:[[self stillImageOutput] connectionWithMediaType:AVMediaTypeVideo]
completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
 CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
 NSLog(@" Metadata %@", (__bridge NSDictionary*)attachments);
 CFRelease(attachments);
 // ...
}];

The same happens when we play with AVCaptureVideoDataOutput delegate method. Each frame is returned with orientation number 1, that means that we need to fix that programmatically, and that is what Apple does in the SquareCam sample code. They get the device orientation and convert it into EXIF.

 /* kCGImagePropertyOrientation values
 The intended display orientation of the image. If present, this key is a CFNumber value with the same value as defined
 by the TIFF and EXIF specifications -- see enumeration of integer constants.
 The value specified where the origin (0,0) of the image is located. If not present, a value of 1 is assumed.
 used when calling featuresInImage: options: The value for this key is an integer NSNumber from 1..8 as found in kCGImagePropertyOrientation.
 If present, the detection will be done based on that orientation but the coordinates in the returned features will still be based on those of the image. */
 enum {
  PHOTOS_EXIF_0ROW_TOP_0COL_LEFT          = 1, //   1  =  0th row is at the top, and 0th column is on the left (THE DEFAULT).
  PHOTOS_EXIF_0ROW_TOP_0COL_RIGHT         = 2, //   2  =  0th row is at the top, and 0th column is on the right.
  PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT      = 3, //   3  =  0th row is at the bottom, and 0th column is on the right.
  PHOTOS_EXIF_0ROW_BOTTOM_0COL_LEFT       = 4, //   4  =  0th row is at the bottom, and 0th column is on the left.
  PHOTOS_EXIF_0ROW_LEFT_0COL_TOP          = 5, //   5  =  0th row is on the left, and 0th column is the top.
  PHOTOS_EXIF_0ROW_RIGHT_0COL_TOP         = 6, //   6  =  0th row is on the right, and 0th column is the top.
  PHOTOS_EXIF_0ROW_RIGHT_0COL_BOTTOM      = 7, //   7  =  0th row is on the right, and 0th column is the bottom.
  PHOTOS_EXIF_0ROW_LEFT_0COL_BOTTOM       = 8  //   8  =  0th row is on the left, and 0th column is the bottom.
 };
 switch (curDeviceOrientation) {
  case UIDeviceOrientationPortraitUpsideDown:  // Device oriented vertically, home button on the top
   exifOrientation = PHOTOS_EXIF_0ROW_LEFT_0COL_BOTTOM;
  break;
  case UIDeviceOrientationLandscapeLeft:       // Device oriented horizontally, home button on the right
   if (isUsingFrontFacingCamera)
    exifOrientation = PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT;
   else
    exifOrientation = PHOTOS_EXIF_0ROW_TOP_0COL_LEFT;
  break;
  case UIDeviceOrientationLandscapeRight:      // Device oriented horizontally, home button on the left
   if (isUsingFrontFacingCamera)
    exifOrientation = PHOTOS_EXIF_0ROW_TOP_0COL_LEFT;
   else
    exifOrientation = PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT;
  break;
  case UIDeviceOrientationPortrait:            // Device oriented vertically, home button on the bottom
  default:
   exifOrientation = PHOTOS_EXIF_0ROW_RIGHT_0COL_TOP;
  break;
}

EXIF dictionary is not supported by all image formats, for instance PNG doesn’t support it. When we create a UIImage from a source (disk, remote) and the image raw data contains this flag is automatically converted into a UIImageOrientation value. This is really important also when we save an image, if we don’t bring it during the saving operation, the saved image will be shown in a wrong way.

Erika Sadun has made a very helpful pack of  methods and functions to use the camera in the right way also dealing with orientations.

Probably the idea behind UIImageOrientation is that it refers to the difference in degrees to apply if we want a match between the current device orientation and the EXIF flag related to a correct image orientation.

Sorry, the comment form is closed at this time.

Related Posts