Photo Editing Extensionメモ＆サンプル

Photo Editing ExtensionはiOS標準の「写真」アプリから呼び出せる、画像編集機能を提供するエクステンションです。

![photo_editing_extension.jpg](https://qiita-image-store.s3.amazonaws.com/0/25727/49d5c169-8305-550e-4e65-84faf662c574.jpeg)

写真にVignette効果を適用するサンプルを作成しました。
https://github.com/imk2o/Try-AppExtensions

## 処理の流れ

* ビューコントローラがインスタンス化される
* 以前の補正データに対応しているか返答する
    * PHContentEditingController#canHandleAdjustmentData(_:)
* 編集開始の準備をする
    * PHContentEditingController#startContentEditingWithInput(_ contentEditingInput: PHContentEditingInput!, placeholderImage placeholderImage: UIImage!)
* 編集を完了する
    * PHContentEditingController#finishContentEditingWithCompletionHandler(_:)
    * PHContentEditingController#cancelContentEditing()

### canHandleAdjustmentData(_:)

以前の補正データが読み込めるのであればtrueを返します。バージョンや補正データの内容から判断することになるはず。

```swift
    func canHandleAdjustmentData(adjustmentData: PHAdjustmentData?) -> Bool {
        guard let adjustmentData = adjustmentData else {
            return false
        }

        // IDとバージョンが一致しているか？
        return adjustmentData.formatIdentifier == self.formatIdentifier && adjustmentData.formatVersion == self.formatVersion
    }
```

### startContentEditingWithInput(_ contentEditingInput: PHContentEditingInput!, placeholderImage placeholderImage: UIImage!)

引数`contentEditingInput`は完了時に必要なので握っておきます。
`placeholderImage`は、プレビュー表示用のイメージが完成するまでの一時的な表示に利用できます。
(前回の編集が**適用された**画像のため、プレビュー用の入力画像には使用しないほうがいいです)

```swift
    func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?, placeholderImage: UIImage) {
        self.input = contentEditingInput

        // 前回編集時の設定したパラメータを取得し、スライダーに反映
        let parameters = NSKeyedUnarchiver.unarchiveObjectWithData(self.input.adjustmentData.data) as? [String: AnyObject]
        if let inputIntensity = parameters?["inputIntensity"] as? Float {
            self.slider.value = inputIntensity
        }

        // プレビュー用の入力CI画像を生成しておく
        if let CGImage = self.input.displaySizeImage?.CGImage {
            self.ciInputImage = CIImage(CGImage: CGImage)
        }
        
        self.updatePreview()
    }
```

### finishContentEditingWithCompletionHandler(_:)

テンプレートどおり、バックグラウンドで最終画像の出力を行います。
`input.fullSizeImageURL`からオリジナル画像を取得し、編集を適用した画像ファイルを
`output.renderedContentURL`に出力します。

`output.adjustmentData`に補正データを出力しておけば、ユーザが再度編集を行う際にパラメータを復元できます。`nil`にはできませんが、空の`NSData`を渡してもエクステンションは機能します。

```swift
    func finishContentEditingWithCompletionHandler(completionHandler: ((PHContentEditingOutput!) -> Void)!) {
        // Render and provide output on a background queue.
        dispatch_async(dispatch_get_global_queue(CLong(DISPATCH_QUEUE_PRIORITY_DEFAULT), 0)) {

            let output = PHContentEditingOutput(contentEditingInput: self.input)
            let inputIntensity = self.currentValue

            // 編集パラメータを構築
            output.adjustmentData = PHAdjustmentData(
                formatIdentifier: self.formatIdentifier,
                formatVersion: self.formatVersion,
                data: NSKeyedArchiver.archivedDataWithRootObject(["inputIntensity": inputIntensity])
            )

            // オリジナル画像を取得し、フィルタを適用
            guard
                let inputImageURL = self.input.fullSizeImageURL,
                let ciInputImage = CIImage(contentsOfURL: inputImageURL)
            else {
                return	// FIXME
            }
            let filteredImage = ciInputImage.createVignetteFilteredImage(inputIntensity, context: self.ciContext)

            // JPEGファイルを出力 (TODO: CIImageから直接JPEGファイルを出力する方法はあるのか？)
            guard let JPEGData = UIImageJPEGRepresentation(filteredImage, 0.8) else {
                return	// FIXME
            }
            JPEGData.writeToURL(output.renderedContentURL, atomically: true)
            
            // Call completion handler to commit edit to Photos.
            completionHandler?(output)
        }
    }
```

## メモ

写真に編集を適用すると、オリジナル画像はオモテに表示されなくなります。
データ自体は残っており、ユーザが「元に戻す」を行うことでオリジナル画像が復元されます。

## 参考

https://www.gaprot.jp/pickup/ios8/photo-editing-extension