Правильно обрезать изображение, полученное из фотобиблиотеки

Я работаю над этим весь день и просмотрел множество вопросов здесь, на SO и в Google, но пока не могу придумать ничего правильного.

Я сделал фотографию на iPad под управлением iOS 5.1.1 и обрезал ее с помощью приложения «Фотографии». Затем я получаю ссылку на него из библиотеки активов и получаю необрезанное изображение с полным разрешением.

Я обнаружил, что информация об обрезке содержится в ключе AdjustmentXMP элемента metadata моего объекта ALAssetRepresentation.

Поэтому я обрезаю фотографию, используя информацию XMP, и вот что я получаю:

Исходное фото (1936 x 2592):
Исходное фото

Правильно обрезанное фото в приложении «Фото» (1420 x 1938):
Правильно обрезанное фото

Фотография обрезана с кодом ниже
(тоже 1420 x 1938, но обрезано примерно на 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, он отображается отлично, но мне нужно полноразмерное изображение.


person lnafziger    schedule 30.12.2012    source источник
comment
Каковы размеры этих изображений (всех трех) и fullResolutionImage? А что такое [rep scale] и [rep orientation]? (Я подозреваю, что StackOverflow изменил размер ваших изображений, но это невозможно знать наверняка.)   -  person Kurt Revis    schedule 30.12.2012
comment
@KurtRevis: Ну, я просто сделал снимки экрана, чтобы показать, как это выглядит, так что не используйте эти изображения. Я просто добавлю размеры к вопросу, спасибо!   -  person lnafziger    schedule 30.12.2012
comment
@KurtRevis: О, и fullResolutionImage — это первое фото. scale равно 1,0, а orientation равно 3. Однако рамка кадрирования должна применяться к неповернутому изображению.   -  person lnafziger    schedule 30.12.2012


Ответы (1)


Это сложно! По-видимому, для этих данных XMP нет документации, поэтому нам придется догадываться, как их интерпретировать. Есть несколько вариантов выбора, и неправильный выбор может привести к слегка неправильным результатам.

TL;DR: Теоретически ваш код выглядит правильно, но на практике он дает неправильный результат, и мы можем попробовать сделать довольно очевидную корректировку.

Ориентация

Файлы изображений могут содержать дополнительные метаданные, определяющие, должны ли (и как) необработанные данные изображения поворачиваться и/или переворачиваться при отображении. UIImage выражает это своей imageOrientation и ALAssetRepresentation похож.

Однако CGImage — это просто растровые изображения, в которых не сохраняется ориентация. -[ALAssetRepresentation fullResolutionImage] дает вам CGImage в исходной ориентации, без каких-либо корректировок.

В вашем случае ориентация 3, что означает ALAssetOrientationRight или UIImageOrientationRight. Программное обеспечение просмотра (например, UIImage) смотрит на это значение, видит, что изображение ориентировано на 90° вправо (по часовой стрелке), затем поворачивает его на 90° влево (против часовой стрелки) перед отображением. Или, говоря иначе, CGImage поворачивается на 90° по часовой стрелке относительно изображения, которое вы видите на экране.

(Чтобы убедиться в этом, получите ширину и высоту CGImage, используя CGImageGetWidth() и CGImageGetHeight(). Вы должны обнаружить, что CGImage имеет ширину 2592 и высоту 1936. Он повернут на 90° относительно ALAssetRepresentation, чье dimensions должно иметь ширину 1936 и высоту 2592. Вы также можете создать UIImage из CGImage, используя обычную ориентацию 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, и совершенно очевидно, как вычислить соответствующий прямоугольник урожая (331,161,1938,1420). Похоже, ваш код делает это правильно.

Если мы обрежем изображение до этого прямоугольника, а затем создадим из него UIImage (указав правильную ориентацию, UIImageOrientationRight), то мы должны получить правильные результаты.

Но результаты неверны! Вы получаете как если бы мы выполняли операции в декартовой системе координат:

диаграмма для декартова случая

В качестве альтернативы, это как если бы изображение было повернуто в противоположном направлении, UIImageOrientationLeft, но мы сохранили тот же прямоугольник обрезки:

диаграмма для случая с ориентацией влево

Коррекция

Все это очень странно, и я не понимаю, что пошло не так, хотя и хотел бы.

Но исправление кажется довольно простым: просто переверните прямоугольник клипа. После вычисления, как указано выше:

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

Это работает? (Для этого случая и для изображений с другой ориентацией?)

person Kurt Revis    schedule 30.12.2012
comment
Это отлично работает для всех ориентаций! Теперь мне только интересно, почему мы должны это делать, и, что более важно, почему это не задокументировано... - person lnafziger; 31.12.2012
comment
У вас есть полный код, который вы можете опубликовать здесь. Я пытаюсь реализовать это решение и, похоже, не могу полностью интерпретировать этот ответ. - person user953175; 29.10.2013