【问题标题】:Photos Extension: Can't Save Images Not Oriented "Up"照片扩展:无法保存未“向上”定向的图像
【发布时间】:2019-02-22 08:30:41
【问题描述】:

TL; DR:iOS 照片编辑扩展无法保存对照片所做的更改,除非它们是在横向左侧方向的设备上拍摄的。


我正在尝试在 iOS 上开发照片编辑扩展。

我的代码基于 Xcode 模板 Apple's sample code 和几个在线可用的教程。

我注意到有些照片在应用更改后无法保存;我得到一个警报视图,内容如下:

无法保存更改

保存时出错。请稍后再试。

好的

在网络上搜索让我在 Stack Overflow 上找到以下两个问题:

  1. iOS photo extension finishContentEditingWithCompletionHandler: Unable to Save Changes(已应用修复,在我的情况下不起作用)
  2. IOS) Photo Extension Unable To Save Changes Issue(问题没有有用的答案)

我尝试了几个编辑扩展程序只是为了确保我的设备没有问题,发现问题出现在:

  1. 我的应用,
  2. Apple 自己的示例代码,
  3. 一些 AppStore 上的第三方应用程序(例如,Litely),但不是其他应用程序(例如,BitCam - 我希望我可以联系向该应用的开发者寻求一些提示...)。

我注意到,对于库中的给定照片资产,问题要么总是出现,要么永远不会出现。也就是说,它似乎取决于正在编辑的照片的某些属性(因此在这种情况下,整个“稍后再试”业务毫无意义)。

我决定在finishContentEditing(completionHandler:)方法内部设置一个断点(调用将修改后的图像保存到框架指定的URL),并检查在编辑开始时传递的PHContentEditingInput对象的各种属性会议。

我很快意识到,使用 iPhone 在纵向、上下颠倒或横向拍摄的照片总是会出现此问题方向,只有这样。在横向左侧(右侧的主页按钮)拍摄的照片可以毫无问题地保存。

Apple 的示例代码所做的是:

  1. 从PHContentEditingInput 实例的fullSizeImageURL 属性创建一个CIIMage 实例。
  2. 通过在其上调用 applyingOrientation() 并传递输入的 fullSizeImageOrientation 属性的值,从点 #1 创建图像的定向副本。
  3. 将适当的 CoreImage 过滤器应用于从点 #2 开始的全尺寸定向图像。
  4. 创建一个CIContext。
  5. 使用上下文调用writeJPEGRepresentation(of:to:colorSpace:),传递#3 中获得的修改后的CIImage、PHContentEditingOutput 中的renderedContentURL 和原始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)
        }
    }
}

(稍微重构后发布在这里。上面的代码块位于 separate 方法中,从调度队列块中调用)


当我尝试编辑使用设备以(例如)横向右方向拍摄的照片时:

...选择苹果的示例代码照片编辑扩展:

...应用“棕褐色”过滤器并点击“完成”:

...我收到了可怕的警报:

...在关闭它之后,图像预览会以某种方式旋转到相对于横向左的方向:

(即以横向拍摄的照片旋转180度,以纵向拍摄的照片旋转90度等)

点击“完成”或“取消”然后“放弃更改”结束会话,图像恢复到正确的方向:

显然,我、Litely 的开发人员和 Apple 的 2016 示例代码都没有意识到一些陷阱(但 BitCam 的开发人员却没有意识到)。

发生了什么事?


解决方法?

如果我用 iPhone 以 纵向 方向拍摄照片并尝试对其进行编辑,则在调试器上 fullSizeImageOrientation 为 .right,并且如前所述编辑失败。

但如果我使用默认工具将图像旋转一次 180 度:

...保存,再次编辑并旋转另一个 180 度(或者,90 + 270 度,但总是在两个单独的编辑),返回到它的原始方向,然后尝试使用扩展进行编辑,现在fullSizeImageOrientation的值是.up,和保存成功。我相信这是因为这个工具实际上会旋转像素数据,而不是仅仅修改方向元数据(事实上它可以在任意角度进行裁剪和旋转 em>,我认为不仅仅是 90 度的倍数...)

当然,这将需要不方便的用户交互,因此它并不是真正的解决方法(不过,程序化的等价物是)。


附录:

我使用的是 Xcode 10.0,以上已在运行 iOS 12 GM 的 iPhone 8 和运行 iOS 11.4.1 的 iPhone 5s 上得到证实。

【问题讨论】:

  • 我的照片扩展似乎没有这个问题,但直到(如你所说)我解决了方向问题......我会告诉你我认为你可能会做错了,但您没有显示任何代码。
  • @matt 谢谢。现在,我基本上是在未经修改的情况下运行 Apple 的示例代码(问题中的链接)。
  • 嗯,这不是很有帮助。我提供了我所做工作的大纲,但您必须弄清楚它与您所做的工作有何不同(如果有的话)。
  • @matt 对不起,我现在将实际代码添加到我的问题中。它是虽然本质上是在“Apple 的示例代码所做的是:...”(5 点项目符号列表)之后所描述的。
  • 好的,只是检查一下! :)

标签: ios swift photosframework photokit


【解决方案1】:

我当然看到保存失败,因为方向的东西是错误的,但以下架构目前似乎对我有用:

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

【讨论】:

  • 我放弃了尝试正确设置MTKView,但我正在对静止图像进行轻量级处理,因此只需使用常规UIImageView 进行预览。
  • 好吧,我也花了一段时间才弄明白!让 CIFilter 渲染出现在正确的位置是一个大问题。请参阅stackoverflow.com/a/51753747/341994 了解一些工作样板。 (我原以为这对于像你这样的 OpenGL 大佬来说是小菜一碟!)
  • 谢谢你一百万次。我无法让您的代码在 Apple 的示例项目中运行,但它确实适用于新项目。我的 OpenGL 现在已经很生锈了(可能会一直这样......)
  • 枪的儿子,它有效,嗯?这很好,因为在我结束这个之前,我已经折腾了很多次。 :)
  • 要点是“无法保存更改”包含很多可能出错的不同事情,因此根本没有提供任何帮助。鉴于此,我完全同意我的问题,没有实际的源代码是“太宽泛”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多