Search code examples
iosswiftphassetphphotolibrary

Swift: PHAssetChangeRequest fails for HEIC image


I want to perform an edit, a JEPG compression for example, on an image from the gallery (taken by iPhone camera) but it fails when the input image is an HEIC image but works with JPEG images.

I retrieve the image within a PHAsset object via UIImagePickerController method:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
            self.asset = asset
        }

        //...
    } 

This function edits the selected image:

func editImage() {
if let _asset = self.asset {
        _asset.requestContentEditingInput(with: nil, completionHandler: { (contentEditingInput, info) in
            let fullURL: URL?
            fullURL = contentEditingInput!.fullSizeImageURL

            let output = PHContentEditingOutput(contentEditingInput:
                contentEditingInput!)
            let archivedData = try? NSKeyedArchiver.archivedData(withRootObject: "HEICEditor", requiringSecureCoding: false)
            let adjustmentData =
                PHAdjustmentData(formatIdentifier:
                    "HEICEditor.App",
                                 formatVersion: "1.0",
                                 data: archivedData!)

            output.adjustmentData = adjustmentData
            let imageData = UIImage.init(contentsOfFile: fullURL!.path)?.jpegData(compressionQuality: 0.5)

            do {
                try imageData!.write(to: output.renderedContentURL, options: .atomic)
            } catch let error {
                print("error writing data:\(error)")
            }

            PHPhotoLibrary.shared().performChanges({
                let request = PHAssetChangeRequest(for: _asset)
                request.contentEditingOutput = output

            }, completionHandler: { (result, error) in
               print("error writing data:\(error)")

            })

        })

    }
}

The project with a sample HEIC image is available at https://github.com/maysamsh/Swift-Playground-EditHEIC

  • Note 1: With EXIF viewers you can find out if the image is HEIC, or after selecting an image and clicking 'Edit Image' button you can see the full name on top of the image preview.

  • Note 2: For some reason when I send the HEIC image from iPhone to Mac and send it back to the iPhone it works on the new copy, which is still HEIC and preserves the the original image orientation.


Solution

  • Here you go, this is how it works: create a CGImageDestination, write the output at .renderedContentURL, perform the PHAssetChangeRequest():

    func editImage() {
        if let _asset = self.asset {
            let options = PHContentEditingInputRequestOptions()
            options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData)
                -> Bool in
                return true
            }
            _asset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
                let fullURL: URL?
                fullURL = contentEditingInput!.fullSizeImageURL
                let output = PHContentEditingOutput(contentEditingInput:
                    contentEditingInput!)
                let archivedData = try? NSKeyedArchiver.archivedData(withRootObject: "HEICEditor", requiringSecureCoding: false)
                let adjustmentData =
                    PHAdjustmentData(formatIdentifier:
                        "HEICEditor.App",
                                     formatVersion: "1.0",
                                     data: archivedData!)
    
    
                let orientation = contentEditingInput?.fullSizeImageOrientation
                let outputURL = output.renderedContentURL
                let cgImage = {
                    () -> CGImage in
                    let image = UIImage.init(contentsOfFile: fullURL!.path)!
                    let imageData = image.jpegData(compressionQuality: 1)
                    let ciImage = CIImage(data: imageData!)!.oriented(forExifOrientation: orientation!)
    
                    return CIContext(options: nil).createCGImage(ciImage, from: ciImage.extent)!
                }()
    
                let cgImageDestination = CGImageDestinationCreateWithURL(outputURL as CFURL, kUTTypeJPEG, 1, nil)!
                CGImageDestinationAddImage(cgImageDestination, cgImage, [
                    kCGImageDestinationLossyCompressionQuality as String:0.7
                    ] as CFDictionary)
                CGImageDestinationFinalize(cgImageDestination)
    
    
                output.adjustmentData = adjustmentData
                self.infoLabel.text = "fullSizeImageURL: \(fullURL?.lastPathComponent ?? "N/A")\n" +
                "renderedContentURL: \(output.renderedContentURL.lastPathComponent)"
    
                PHPhotoLibrary.shared().performChanges({
                    let request = PHAssetChangeRequest(for: _asset)
                    request.contentEditingOutput = output
                }, completionHandler: { (result, error) in
                    print("result: \(result), error: \(String(describing: error))")
                })
    
            })
    
        }
    }