This is a tricky one! There is apparently no documentation for this XMP data, so we'll have to guess at how to interpret it. There are a number of choices to make, and getting it wrong can lead to subtly wrong results.
TL;DR: In theory your code looks correct, but in practice it's giving the wrong result, and there's a fairly obvious adjustment we can try.
Orientation
Image files may contain additional metadata specifying whether (and how) the raw data of the image should be rotated and/or flipped when displayed. UIImage
expresses this with its imageOrientation
property, and ALAssetRepresentation
is similar.
However, CGImage
s are just bitmaps, with no orientation stored in them. -[ALAssetRepresentation fullResolutionImage]
gives you a CGImage
in the original orientation, with no adjustments applied.
In your case, the orientation is 3
, meaning ALAssetOrientationRight
or UIImageOrientationRight
. The viewing software (for instance, UIImage
) looks at this value, sees that the image is oriented 90° to the right (clockwise), then rotates it by 90° to the left (counterclockwise) before displaying it. Or, to say it another way, the CGImage
is rotated 90° clockwise from the image you're looking at on your screen.
(To verify this, get the width and height of the CGImage by using CGImageGetWidth()
and CGImageGetHeight()
. You should find that the CGImage is 2592 wide and 1936 high. This is rotated 90° from the ALAssetRepresentation
, whose dimensions
should be 1936 wide by 2592 high. You could also create a UIImage
from the CGImage
using the normal orientation UIImageOrientationUp
, write the UIImage
to a file, and see what it looks like.)
The values in the XMP dictionary appear to be relative to the CGImage
's orientation. For instance, the crop rect is wider than it is tall, the X translation is greater than the Y translation, etc. Makes sense.
Coordinate system
We also have to decide what coordinate system the XMP values are supposed to be in. Most likely it's one of these two:
- "Cartesian": origin is at the bottom-left corner of the image, X increases to the right, and Y increases upwards. This is system that Core Graphics usually uses.
- "Flipped": origin is at the top-left corner of the image, X increases to the right, and Y increases downwards. This is the system that UIKit usually uses. Surprisingly, unlike most of CG,
CGImageCreateWithImageInRect()
interprets itsrect
argument this way.
Let's assume that "flipped" is correct, since it's generally more convenient. Your code is already trying to do it that way, anyway.
Interpreting the XMP dictionary
The dictionary contains an affine transform and a crop rect. Let's guess that it should be interpreted in this order:
- Apply the transform
- Draw the image in its natural rect (0,0,w,h)
- Un-apply the transform (pop the transform stack)
- Crop to the crop rect
If we try this by hand, the numbers seem to work out. Here's a rough diagram, with the crop rect in translucent purple:
Now for some code
We don't actually have to follow those exact steps, in terms of calling CG, but we should act as if we had.
We just want to call CGImageCreateWithImageInRect
, and it's pretty obvious how to compute the appropriate crop rect (331,161,1938,1420)
. Your code appears to do this correctly.
If we crop the image to that rect, then create a UIImage
from it (specifying the correct orientation, UIImageOrientationRight
), then we should get the correct results.
But, the results are wrong! What you get was as if we did the operations in a Cartesian coordinate system:
Alternatively, it's as if the image was rotated the opposite direction, UIImageOrientationLeft
, but we kept the same crop rect:
A correction
That's all very odd, and I don't understand what went wrong, although I'd love to.
But a fix seems fairly straightforward: just flip the clip rect. After computing it as above:
// flip the transformedCropBox in the image
transformedCropBox.origin.y = CGImageGetHeight(defaultImage) - CGRectGetMaxY(transformedCropBox);
Does that work? (For this case, and for images with other orientations?)