TL; DR: Расширение для редактирования фотографий iOS не может сохранить изменения в фотографиях, если только они не были сняты с помощью устройства в горизонтальной левой ориентации.
Я пытаюсь разработать расширение для редактирования фотографий на iOS.
Я создал свой код на основе шаблона Xcode, пример кода Apple и несколько руководств, доступных в Интернете.
Я заметил, что некоторые фотографии не сохраняются после применения изменений; Я получаю предупреждение, которое гласит:
Не удалось сохранить изменения
Произошла ошибка при сохранении. Пожалуйста, повторите попытку позже.
ОК
Поиск в Интернете привел меня к двум следующим вопросам здесь, в Stack Overflow:
- расширение iOS для фотографий FinishContentEditingWithCompletionHandler: невозможно сохранить изменения (уже применено исправление, не работает в моем случае)
- IOS) Расширение для фотографий не может сохранить изменения (нет полезных ответов на вопрос)
Я попробовал несколько расширений для редактирования, чтобы убедиться, что с моим устройством все в порядке, и обнаружил, что проблема возникает с:
- Мое приложение,
- Собственный пример кода Apple,
- Некоторые сторонние приложения в AppStore (например, Litely), но не другие (например, BitCam. Я хотел бы связаться с разработчики этого приложения, чтобы попросить несколько советов...).
Я заметил, что для данного фоторесурса из библиотеки проблема возникает либо всегда, либо никогда. То есть это как бы зависит от какого-то свойства редактируемой фотографии (поэтому вся эта затея с "Попробовать позже" в данном случае бессмысленна).
Я решил установить точку останова внутри метода finishContentEditing(completionHandler:)
(вызванного для сохранения измененного изображения по URL-адресу, указанному фреймворком), и проверить различные свойства объекта PHContentEditingInput
, переданного в начале сеанса редактирования.
Я быстро понял, что проблема всегда возникает со снимками, сделанными на iPhone в ориентации Портрет, Портрет вверх ногами или Пейзаж вправо. только тогда. Фотографии, сделанные в Пейзаж слева (кнопка «Домой» справа), могут быть сохранены без проблем.
Что делает пример кода Apple:
- Создайте экземпляр
CIIMage
из свойстваfullSizeImageURL
экземпляраPHContentEditingInput
. - Создайте ориентированную копию изображения из точки №1, вызвав для нее
applyingOrientation()
и передав значение свойстваfullSizeImageOrientation
входных данных. - Примените соответствующий фильтр CoreImage к полноразмерному ориентированному изображению из точки №2.
- Создайте
CIContext
. - Используйте контекст для вызова
writeJPEGRepresentation(of:to:colorSpace:)
, передавая модифицированный CIImage, полученный в #3,renderedContentURL
изPHContentEditingOutput
и цветовое пространство исходногоCIImage
.
Фактический код:
DispatchQueue.global(qos: .userInitiated).async {
// Load full-size image to process from input.
guard let url = input.fullSizeImageURL
else { fatalError("missing input image url") }
guard let inputImage = CIImage(contentsOf: url)
else { fatalError("can't load input image to apply edit") }
// Define output image with Core Image edits.
let orientedImage = inputImage//.applyingOrientation(input.fullSizeImageOrientation)
let outputImage: CIImage
switch selectedFilterName {
case .some(wwdcFilter):
outputImage = orientedImage.applyingWWDCDemoEffect()
case .some(let filterName):
outputImage = orientedImage.applyingFilter(filterName, parameters: [:])
default:
outputImage = orientedImage
}
// Usually you want to create a CIContext early and reuse it, but
// this extension uses one (explicitly) only on exit.
let context = CIContext()
// Render the filtered image to the expected output URL.
if #available(OSXApplicationExtension 10.12, iOSApplicationExtension 10.0, *) {
// Use Core Image convenience method to write JPEG where supported.
do {
try context.writeJPEGRepresentation(of: outputImage, to: output.renderedContentURL, colorSpace: inputImage.colorSpace!)
completionHandler(output)
} catch let error {
NSLog("can't write image: \(error)")
completionHandler(nil)
}
} else {
// Use CGImageDestination to write JPEG in older OS.
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent)
else { fatalError("can't create CGImage") }
guard let destination = CGImageDestinationCreateWithURL(output.renderedContentURL as CFURL, kUTTypeJPEG, 1, nil)
else { fatalError("can't create CGImageDestination") }
CGImageDestinationAddImage(destination, cgImage, nil)
let success = CGImageDestinationFinalize(destination)
if success {
completionHandler(output)
} else {
completionHandler(nil)
}
}
}
(небольшой рефакторинг для размещения здесь. Большая часть приведенного выше кода находится в отдельном методе, вызываемом из блока очереди отправки)
Когда я пытаюсь отредактировать фотографию, сделанную с помощью устройства, скажем, в ориентации Пейзаж вправо:
...выбрав пример кода Apple Photo Editing Extension:
...применив фильтр «Сепия» и нажав «Готово»:
...Я получаю ужасное предупреждение:
... и после его отклонения предварительный просмотр изображения каким-то образом поворачивается в ориентацию относительно альбомной слева:
(например, фотография, сделанная в режиме Пейзаж справа, повернута на 180 градусов, фотография, сделанная в режиме Портрет, повернута на 90 градусов и т. д.)
Нажатие «Готово» или «Отмена», а затем «Отменить изменения» завершает сеанс, и изображение восстанавливает свою правильную ориентацию:
Очевидно, есть некоторая ловушка, о которой не знаю ни я, ни разработчики Litely, ни пример кода Apple 2016 года (но знают разработчики BitCam).
Что происходит?
Обходной путь?
Если я сделаю снимок с iPhone в портретной ориентации и попытаюсь отредактировать его, в отладчике fullSizeImageOrientation
будет .right
, и редактирование завершится ошибкой, как только что было описано.
Но если я поверну изображение один раз на 180 градусов, используя инструмент по умолчанию:
...сохранение, редактирование снова и поворот еще на 180 градусов (или, как вариант, на 90 + 270 градусов, но всегда в два отдельных редактирования), возвращая его обратно в исходное >исходную ориентацию и затем попробуйте отредактировать с помощью расширения, теперь значение fullSizeImageOrientation
равно .up
, и сохранение выполнено успешно. Я полагаю, это потому, что этот инструмент фактически поворачивает пиксельные данные, а не просто изменяет метаданные ориентации (тот факт, что он может обрезать и поворачивать под произвольными углами em>, не просто кратные 90 градусам, я думаю, это выдает...)
Конечно, это потребует неудобного взаимодействия с пользователем, поэтому на самом деле это не обходной путь (хотя программный эквивалент будет).
Приложение:
Я использую Xcode 10.0, и вышеизложенное было подтверждено как на iPhone 8 под управлением iOS 12 GM, так и на iPhone 5s под управлением iOS 11.4.1).
orientedImage = inputImage
(CIImage, взятый из растровых данных по URL-адресу), а я говорюvar ci = CIImage(contentsOf: inurl, options: [.applyOrientationProperty:true])!
. - person matt   schedule 19.09.2018.applyingOrientation(input.fullSizeImageOrientation)
, чтобы посмотреть, имеет ли это значение (нет), и забыл вернуть ее обратно. - person Nicolas Miari   schedule 19.09.2018finishContentEditing(completionHandler:)
разветвляется на три отдельных метода. Но данные настройки устанавливаются до ветвления и, следовательно, не включены в опубликованный мной код. - person Nicolas Miari   schedule 19.09.2018