正确裁剪从照片库中获取的图像

时间:2021-12-26 08:38:42

I've been working on this all day, and have looked at lots of questions here on SO and google, but so far I can't come up with anything quite right.

我一整天都在研究这个问题,并且已经在SO和google上看了很多问题,但到目前为止我还没有提出任何正确的建议。

I have taken a photo on an iPad running iOS 5.1.1 and cropped it using the Photos app. I then get a reference to it from the assets library and am getting the full resolution image which is un-cropped.

我在运行iOS 5.1.1的iPad上拍了一张照片,并使用照片应用裁剪了它。然后我从资产库中获取它的引用,并获得未裁剪的全分辨率图像。

I've found that the cropping information is contained in the AdjustmentXMP key of metadata on my ALAssetRepresentation object.

我发现裁剪信息包含在ALAssetRepresentation对象的metadataXMP键中。

So I crop the photo using the XMP info and here is what I get:

所以我使用XMP信息裁剪照片,这是我得到的:

Original Photo (1,936 x 2,592):
正确裁剪从照片库中获取的图像

原始照片(1,936 x 2,592):

Properly Cropped Photo, as seen in the Photos App (1,420 x 1,938):
正确裁剪从照片库中获取的图像

正确裁剪的照片,如照片应用程序(1,420 x 1,938)中所示:

Photo Cropped With Code Below
(also 1,420 x 1,938 but cropped roughly 200 pixels too far to the right):
正确裁剪从照片库中获取的图像

使用下面的代码裁剪照片(也是1,420 x 1,938,但是右边的距离大约200像素):

This is the XMP data from the photo:

这是照片中的XMP数据:

<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:aas="http://ns.apple.com/adjustment-settings/1.0/">
         <aas:AffineA>1</aas:AffineA>
         <aas:AffineB>0</aas:AffineB>
         <aas:AffineC>0</aas:AffineC>
         <aas:AffineD>1</aas:AffineD>
         <aas:AffineX>-331</aas:AffineX>
         <aas:AffineY>-161</aas:AffineY>
         <aas:CropX>0</aas:CropX>
         <aas:CropY>0</aas:CropY>
         <aas:CropW>1938</aas:CropW>
         <aas:CropH>1420</aas:CropH>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>

Here is the code that I am using to crop the photo:

这是我用于裁剪照片的代码:

ALAssetRepresentation *rep = // Get asset representation
CGImageRef defaultImage = [rep fullResolutionImage];

// Values obtained from XMP data above:
CGRect cropBox = CGRectMake(0, 0, 1938, 1420);
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, 1, 331, 161);

// Apply the Affine Transform to the crop box:
CGRect transformedCropBox =  CGRectApplyAffineTransform(cropBox, transform);

// Created a new cropped image:
CGImageRef croppedImage = CGImageCreateWithImageInRect(defaultImage, transformedCropBox);

// Create the UIImage:
UIImage *image = [UIImage imageWithCGImage:croppedImage scale:[rep scale] orientation:[rep orientation]];

CGImageRelease(croppedImage);

I've reproduced the problem with multiple images. If I just use the fullScreenImage it displays perfectly, but I need the full size image.

我用多张图片重现了这个问题。如果我只使用fullScreenImage它会完美显示,但我需要全尺寸图像。

1 个解决方案

#1


11  

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.

这是一个棘手的问题!显然没有关于此XMP数据的文档,因此我们必须猜测如何解释它。有许多选择要做,而错误的选择会导致产生微妙的错误结果。

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.

TL; DR:理论上你的代码看起来是正确的,但实际上它给出了错误的结果,我们可以尝试一个相当明显的调整。

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.

图像文件可以包含额外的元数据,指定在显示时是否(以及如何)旋转和/或翻转图像的原始数据。 UIImage使用imageOrientation属性表示这一点,并且ALAssetRepresentation类似。

However, CGImages are just bitmaps, with no orientation stored in them. -[ALAssetRepresentation fullResolutionImage] gives you a CGImage in the original orientation, with no adjustments applied.

但是,CGImages只是位图,没有存储方向。 - [ALAssetRepresentation fullResolutionImage]为您提供原始方向的CGImage,不应用任何调整。

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.

在您的情况下,方向是3,意味着ALAssetOrientationRight或UIImageOrientationRight。查看软件(例如,UIImage)查看此值,看到图像向右90度(顺时针),然后向左旋转90度(逆时针),然后再显示它。或者,换句话说,CGImage从您在屏幕上看到的图像顺时针旋转90°。

(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.)

(为了验证这一点,使用CGImageGetWidth()和CGImageGetHeight()获取CGImage的宽度和高度。你应该发现CGImage是2592宽和1936高。这是从ALAssetRepresentation旋转了90°,其尺寸应该是1936您还可以使用正常方向UIImageOrientationUp从CGImage创建UIImage,将UIImage写入文件,然后查看它的外观。)

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.

XMP字典中的值似乎与CGImage的方向相关。例如,裁剪矩形比它高,X平移大于Y平移等。有意义。

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:

我们还必须决定XMP值应该在哪个坐标系中。很可能它是这两个中的一个:

  • "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.
  • “笛卡儿”:原点位于图像的左下角,X向右增加,Y向上增加。这是Core Graphics通常使用的系统。

  • "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 its rect argument this way.
  • “翻转”:原点位于图像的左上角,X向右增加,Y向下增加。这是UIKit通常使用的系统。令人惊讶的是,与大多数CG不同,CGImageCreateWithImageInRect()以这种方式解释其rect参数。

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:

字典包含仿射变换和裁剪矩形。我们猜测应该按此顺序解释:

  1. Apply the transform
  2. 应用转换

  3. Draw the image in its natural rect (0,0,w,h)
  4. 以自然矩形(0,0,w,h)绘制图像

  5. Un-apply the transform (pop the transform stack)
  6. 取消应用转换(弹出转换堆栈)

  7. Crop to the crop rect
  8. 裁剪到裁剪矩形

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.

在调用CG时,我们实际上不必遵循这些确切的步骤,但我们应该像我们一样行事。

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.

我们只想调用CGImageCreateWithImageInRect,并且很明显如何计算适当的crop rect(331,161,1938,1420)。您的代码似乎正确执行此操作。

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.

如果我们将图像裁剪到该矩形,然后从中创建一个UIImage(指定正确的方向,UIImageOrientationRight),那么我们应该得到正确的结果。

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:

或者,就像图像旋转方向相反,UIImageOrientationLeft,但我们保持相同的裁剪矩形:

正确裁剪从照片库中获取的图像

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?)

那样有用吗? (对于这种情况,以及其他方向的图像?)

#1


11  

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.

这是一个棘手的问题!显然没有关于此XMP数据的文档,因此我们必须猜测如何解释它。有许多选择要做,而错误的选择会导致产生微妙的错误结果。

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.

TL; DR:理论上你的代码看起来是正确的,但实际上它给出了错误的结果,我们可以尝试一个相当明显的调整。

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.

图像文件可以包含额外的元数据,指定在显示时是否(以及如何)旋转和/或翻转图像的原始数据。 UIImage使用imageOrientation属性表示这一点,并且ALAssetRepresentation类似。

However, CGImages are just bitmaps, with no orientation stored in them. -[ALAssetRepresentation fullResolutionImage] gives you a CGImage in the original orientation, with no adjustments applied.

但是,CGImages只是位图,没有存储方向。 - [ALAssetRepresentation fullResolutionImage]为您提供原始方向的CGImage,不应用任何调整。

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.

在您的情况下,方向是3,意味着ALAssetOrientationRight或UIImageOrientationRight。查看软件(例如,UIImage)查看此值,看到图像向右90度(顺时针),然后向左旋转90度(逆时针),然后再显示它。或者,换句话说,CGImage从您在屏幕上看到的图像顺时针旋转90°。

(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.)

(为了验证这一点,使用CGImageGetWidth()和CGImageGetHeight()获取CGImage的宽度和高度。你应该发现CGImage是2592宽和1936高。这是从ALAssetRepresentation旋转了90°,其尺寸应该是1936您还可以使用正常方向UIImageOrientationUp从CGImage创建UIImage,将UIImage写入文件,然后查看它的外观。)

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.

XMP字典中的值似乎与CGImage的方向相关。例如,裁剪矩形比它高,X平移大于Y平移等。有意义。

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:

我们还必须决定XMP值应该在哪个坐标系中。很可能它是这两个中的一个:

  • "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.
  • “笛卡儿”:原点位于图像的左下角,X向右增加,Y向上增加。这是Core Graphics通常使用的系统。

  • "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 its rect argument this way.
  • “翻转”:原点位于图像的左上角,X向右增加,Y向下增加。这是UIKit通常使用的系统。令人惊讶的是,与大多数CG不同,CGImageCreateWithImageInRect()以这种方式解释其rect参数。

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:

字典包含仿射变换和裁剪矩形。我们猜测应该按此顺序解释:

  1. Apply the transform
  2. 应用转换

  3. Draw the image in its natural rect (0,0,w,h)
  4. 以自然矩形(0,0,w,h)绘制图像

  5. Un-apply the transform (pop the transform stack)
  6. 取消应用转换(弹出转换堆栈)

  7. Crop to the crop rect
  8. 裁剪到裁剪矩形

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.

在调用CG时,我们实际上不必遵循这些确切的步骤,但我们应该像我们一样行事。

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.

我们只想调用CGImageCreateWithImageInRect,并且很明显如何计算适当的crop rect(331,161,1938,1420)。您的代码似乎正确执行此操作。

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.

如果我们将图像裁剪到该矩形,然后从中创建一个UIImage(指定正确的方向,UIImageOrientationRight),那么我们应该得到正确的结果。

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:

或者,就像图像旋转方向相反,UIImageOrientationLeft,但我们保持相同的裁剪矩形:

正确裁剪从照片库中获取的图像

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?)

那样有用吗? (对于这种情况,以及其他方向的图像?)