Расширение фотографий: невозможно сохранить изображения, не ориентированные "вверх" - PullRequest
0 голосов
/ 18 сентября 2018

TL;DR: Расширение редактирования фотографий iOS не может сохранить изменения в фотографиях, если они не были сделаны устройством с альбомной ориентацией влево .


Я пытаюсь разработать расширение для редактирования фотографийна iOS.

Я основал свой код на шаблоне Xcode, Образец кода Apple и несколько учебных пособий, доступных в Интернете.

Я заметил, что некоторые фотографии не удается сохранитьпосле применения изменений;Я получаю предупреждение , которое гласит:

Невозможно сохранить изменения

Произошла ошибка при сохранении.Пожалуйста, повторите попытку позже.

OK

Поиск в Интернете привел меня к двум следующим вопросам здесь, о переполнении стека:

  1. Расширение фотографии для iOSНет полезных ответов на вопрос)

Я попробовал несколько расширений для редактирования, просто чтобы убедиться, что с моим устройством что-то не так, и обнаружил, что проблема возникает с:

  1. Мое приложение,
  2. Пример кода Apple ,
  3. Некоторые сторонних приложений в AppStore (например, * 1053)* Little ), но не другие (например, BitCam - хотелось бы связаться с разработчиками этого приложения и попросить несколько советов ...).

Я заметил, что для данного фоторепортажа из библиотеки проблема всегда возникает или никогда не возникает.То есть, похоже, что это зависит от некоторого свойства редактируемой фотографии (поэтому весь бизнес «Попробуй позже» в этом случае не имеет смысла).

Я решил установить точку останова внутри метода finishContentEditing(completionHandler:) (вызываемого для сохранения измененного изображения по URL-адресу, указанному в платформе) и проверить различные свойства объекта PHContentEditingInput, переданного в началесеанса редактирования.

Я быстро понял, что проблема всегда возникает с изображениями, снятыми на iPhone в Портрет , Портрет вверх ногами или Пейзаж вправо ориентации, и только тогда.Фотографии, сделанные в Пейзаж влево (кнопка «Домой» справа), могут быть сохранены без проблем.

Пример кода Apple:

  1. Создание CIIMage экземпляр из свойства fullSizeImageURL экземпляра PHContentEditingInput.
  2. Создайте ориентированную копию изображения из точки # 1, вызвав на нем applyingOrientation(), передав значениеfullSizeImageOrientation свойство ввода.
  3. Примените соответствующий фильтр CoreImage к полноразмерному ориентированному изображению из точки # 2.
  4. Создайте CIContext.
  5. Используйте контекст для вызова 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)
        }
    }
}

(слегка реорганизовано для публикации здесь. Блюк кода выше находится в отдельном методе, вызываемом из блока очереди отправки)


Когда я пытаюсь отредактировать фотографию, снятую устройством, в (скажем) Пейзаж вправо ориентация:

enter image description here

... выбор образца кода Apple Расширение редактирования фотографий:

enter image description here

... применяя фильтр "Сепия" и нажимая "Готово":

enter image description here

... Я получаю ужасalert:

enter image description here

... и после его отклонения предварительный просмотр изображения каким-то образом поворачивается к ориентации относительно ландшафта влево:

enter image description here

(т. Е. Фотография, сделанная в Пейзаж вправо , повернута на 180 градусов, фотография, сделанная в Портрет , являетсяповернут на 90 градусов и т. д.)

Нажатие «Готово» или «Отмена», а затем «Отмена изменений» завершает сеанс, и изображение восстанавливается в правильной ориентации:

enter image description here

Очевидно, есть некоторая ловушка, о которой не знают ни я, ни разработчики Little , ни образец кода Apple 2016 (но разработчики BitCam знают).

Что происходит?


Обход

Если я сделаю снимок с iPhone в книжной ориентации и попытаюсь отредактировать его, на отладчике fullSizeImageOrientation будет .right, и редактирование завершится неудачно, как только что описано.

Но если я поверну изображение один раз на 180 градусов, используя инструмент по умолчанию:

enter image description here

... сохранение, редактирование снова и вращение еще на 180 градусов (или, альтернативно, 90 + 270 градусов, но всегда в двух отдельных правках ), возвращая его обратно в исходная ориентация и , затем попытаться редактировать с использованием расширения, теперь значение fullSizeImageOrientation равно .up, и сохранение завершено, Я считаю, что это потому, что этот инструмент фактически вращает данные пикселей вместо того, чтобы просто изменять ориентацию метаданные (тот факт, что он может обрезать и поворачивать под произвольные углы , не просто кратные 90 градусов, я думаю, что это отдает ...)

Конечно, это потребует неудобного взаимодействия с пользователем, так что это не обходной путь (хотя программный эквивалент будет).


Добавление:

Я использую Xcode 10.0, и это подтверждается как на iPhone 8 под управлением iOS 12 GM, так и на iPhone 5 под управлением iOS 11.4.1).

1 Ответ

0 голосов
/ 19 сентября 2018

Я, конечно, видел сбой сохранения, потому что ориентация была неправильной, но в настоящее время мне кажется, что работает следующая архитектура:

func startContentEditing(with contentEditingInput: PHContentEditingInput, placeholderImage: UIImage) {
    self.input = contentEditingInput
    if let im = self.input?.displaySizeImage {
        self.displayImage = CIImage(image:im, options: [.applyOrientationProperty:true])!
        // ... other stuff depending on what the adjustment data was ...
    }
    self.mtkview.setNeedsDisplay()
}
func finishContentEditing(completionHandler: @escaping ((PHContentEditingOutput?) -> Void)) {
    DispatchQueue.global(qos:.default).async {
        let inurl = self.input!.fullSizeImageURL!
        let output = PHContentEditingOutput(contentEditingInput:self.input!)
        let outurl = output.renderedContentURL
        var ci = CIImage(contentsOf: inurl, options: [.applyOrientationProperty:true])!
        let space = ci.colorSpace!
        // ... apply real filter to `ci` based on user edits ...
        try! CIContext().writeJPEGRepresentation(
            of: ci, to: outurl, colorSpace: space)
        let data = // whatever
        output.adjustmentData = PHAdjustmentData(
            formatIdentifier: self.myidentifier, formatVersion: "1.0", data: data)
        completionHandler(output)
    }
}
...