8

我整天都在研究这个问题,并且在 SO 和 google 上查看了很多问题,但到目前为止我还没有想出任何正确的答案。

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

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

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

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

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

用下面的代码裁剪的照片
(也是 1,420 x 1,938,但裁剪得离右边太远了大约 200 像素):
问题

这是照片中的 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>

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

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

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

4

1 回答 1

11

这是一个棘手的问题!这个 XMP 数据显然没有文档,所以我们必须猜测如何解释它。有许多选择要做,如果做错了可能会导致微妙的错误结果。

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

方向

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

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

在您的情况下,方向是3, 意思ALAssetOrientationRightUIImageOrientationRight。查看软件(例如UIImage)查看此值,发现图像向右(顺时针)旋转 90°,然后在显示之前将其向左旋转 90°(逆时针)。或者,换一种说法,CGImage从您在屏幕上看到的图像顺时针旋转 90°。

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

XMP 字典中的值似乎与CGImage' 的方向有关。例如,裁剪矩形的宽度大于高度,X 平移大于 Y 平移,等等。这是有道理的。

坐标系

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

  • “笛卡尔”:原点在图像的左下角,X向右增加,Y向上增加。这是 Core Graphics 通常使用的系统。
  • “翻转”:原点在图像的左上角,X向右增加,Y向下增加。这是 UIKit 通常使用的系统。令人惊讶的是,与大多数 CG 不同,它以这种方式CGImageCreateWithImageInRect()解释其rect论点。

让我们假设“翻转”是正确的,因为它通常更方便。无论如何,您的代码已经在尝试这样做。

解释 XMP 字典

该字典包含一个仿射变换和一个裁剪矩形。让我们猜测它应该按以下顺序解释:

  1. 应用变换
  2. 以自然矩形 (0,0,w,h) 绘制图像
  3. 取消应用变换(弹出变换堆栈)
  4. 裁剪到裁剪矩形

如果我们手动尝试,数字似乎可以解决。这是一个粗略的图表,裁剪矩形为半透明紫色:

翻转案例示意图

现在一些代码

在调用 CG 方面,我们实际上不必遵循这些确切的步骤,但我们应该表现得好像我们有那样。

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

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

但是,结果是错误的! 你得到的就像我们在笛卡尔坐标系中进行操作一样:

笛卡尔案例图

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

左向案例图

更正

这一切都很奇怪,我不明白出了什么问题,尽管我很乐意。

但修复似乎相当简单:只需翻转剪辑矩形。如上计算后:

// flip the transformedCropBox in the image
transformedCropBox.origin.y = CGImageGetHeight(defaultImage) - CGRectGetMaxY(transformedCropBox);

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

于 2012-12-30T11:21:13.310 回答