简体   繁体   English

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

[英]Properly crop an image obtained from the photo library

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. 我在运行iOS 5.1.1的iPad上拍了一张照片,并使用照片应用裁剪了它。 I then get a reference to it from the assets library and am getting the full resolution image which is un-cropped. 然后我从资产库中获取它的引用,并获得未裁剪的全分辨率图像。

I've found that the cropping information is contained in the AdjustmentXMP key of metadata on my ALAssetRepresentation object. 我发现,在裁剪信息包含在AdjustmentXMP的关键metadata我对ALAssetRepresentation对象。

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它会完美显示,但我需要全尺寸图像。

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. 显然没有关于此XMP数据的文档,因此我们必须猜测如何解释它。 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. 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, CGImage s are just bitmaps, with no orientation stored in them. 但是, CGImage只是位图,没有存储方向。 -[ALAssetRepresentation fullResolutionImage] gives you a CGImage in the original orientation, with no adjustments applied. -[ALAssetRepresentation fullResolutionImage]为您提供原始方向的CGImage ,不应用任何调整。

In your case, the orientation is 3 , meaning ALAssetOrientationRight or UIImageOrientationRight . 在您的情况下,方向为3 ,表示ALAssetOrientationRightUIImageOrientationRight 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. 查看软件(例如, UIImage )查看此值,看到图像向右90度(顺时针),然后向左旋转90度(逆时针),然后再显示它。 Or, to say it another way, the CGImage is rotated 90° clockwise from the image you're looking at on your screen. 或者,换句话说, 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°,其dimensions应该是1936您还可以使用正常方向UIImageOrientationUpCGImage创建UIImage ,将UIImage写入文件,然后查看它的外观。)

The values in the XMP dictionary appear to be relative to the CGImage 's orientation. XMP字典中的值似乎与CGImage的方向相关。 For instance, the crop rect is wider than it is tall, the X translation is greater than the Y translation, etc. Makes sense. 例如,裁剪矩形比它高,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. “笛卡儿” :原点位于图像的左下角,X向右增加,Y向上增加。 This is system that Core Graphics usually uses. 这是Core Graphics通常使用的系统。
  • "Flipped": origin is at the top-left corner of the image, X increases to the right, and Y increases downwards. “翻转”:原点位于图像的左上角,X向右增加,Y向下增加。 This is the system that UIKit usually uses. 这是UIKit通常使用的系统。 Surprisingly, unlike most of CG, CGImageCreateWithImageInRect() interprets its rect argument this way. 令人惊讶的是,与大多数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 解释XMP词典

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. Draw the image in its natural rect (0,0,w,h) 以自然矩形(0,0,w,h)绘制图像
  3. Un-apply the transform (pop the transform stack) 取消应用转换(弹出转换堆栈)
  4. 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. 在调用CG时,我们实际上不必遵循这些确切的步骤,但我们应该像我们一样行事。

We just want to call CGImageCreateWithImageInRect , and it's pretty obvious how to compute the appropriate crop rect (331,161,1938,1420) . 我们只想调用CGImageCreateWithImageInRect ,而且很明显如何计算合适的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. 如果我们将图像裁剪到该矩形,然后从中创建一个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?) (对于这种情况,以及其他方向的图像?)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM