[iOS 10] CoreImageのCIFilterを試してみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CoreImage

こんにちは!
モバイルアプリサービス部の田中孝明です。

CoreImageについては歴史が古く、iOS 5のころから存在し、バージョンが上がるたびに拡張されてきたフレームワークです。
iOS 10においても、WWDC 2016のセッションLive Photo Editing and RAW Processing with Core Imageで解説されていますとおり、新規に拡張された機能を確認することができます。

現時点で180を超えるフィルターと、RAWイメージの加工がサポートされており、画像編集アプリをよりリッチに彩ることができるようになっています。

WWDC 2016でも触れられていましたが、サンプルコード「RawExpose: Using CIRAWFilter to Decode RAW Images」で機能を簡単に試すことができました。
しかし、執筆当時の9/14時点では公開が停止されています。

CIFilter

画像の加工をする際はCIFilter経由で行います。
以下のコードはアルバム内の写真をPhotos.frameworkの機能を使ってPHAssetとして取得し、 PHImageManagerを使用して写真の情報を取得します。
取得した写真の情報をもとにCIFilterを生成しています。

var asset : PHAsset?
var originalTemp : Float = 0.0
var originalTint : Float = 0.0
var ciRawFilter : CIFilter?

...

PHImageManager.default().requestImageData(for: asset, options: options) { imageData, dataUTI, _, _ in
    guard let imageData = imageData, let dataUTI = dataUTI else { return }

    let rawOptions = [ String(kCGImageSourceTypeIdentifierHint) : dataUTI ]
    self.ciRawFilter = CIFilter(imageData: imageData as Data, options: rawOptions)

    guard let ciRawFilter = self.ciRawFilter else { return }

    if let value = ciRawFilter.value(forKey: kCIOutputNativeSizeKey) as? CIVector {
        self.imageNativeSize = CGSize(width: value.x, height: value.y)
    }

    if let value = ciRawFilter.value(forKey: kCIInputNeutralTemperatureKey) {
        self.originalTemp = (value as AnyObject).floatValue
        self.tempSlider.setValue((value as AnyObject).floatValue, animated: false)
    }

    if let value = ciRawFilter.value(forKey: kCIInputNeutralTintKey) {
        self.originalTint = (value as AnyObject).floatValue
        self.tintSlider.setValue((value as AnyObject).floatValue, animated: false)
    }
}


CIFilterの結果をリアルタイムに画面に反映させるためにGLKViewを用います。この辺りはサンプルコード「RawExpose: Using CIRAWFilter to Decode RAW Images」を参考にしています。

class ImageViewController: UIViewController {
    @IBOutlet weak var imageView : GLKView!
    var ciContext : CIContext?

    ...

    imageView.context = EAGLContext(api : .openGLES3)
    ciContext = CIContext(eaglContext: imageView.context, options: [kCIContextWorkingFormat : Int(kCIFormatRGBAh)])

    ...
}

// MARK: - GLKViewDelegate
extension ImageViewController: GLKViewDelegate {
    func glkView(_ view: GLKView, drawIn rect: CGRect) {
        guard let context = ciContext, let ciRawFilter = ciRawFilter, let imageNativeSize = imageNativeSize else { return }

        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))

        glEnable(GLenum(GL_BLEND))
        glBlendFunc(GLenum(GL_ONE), GLenum(GL_ONE_MINUS_SRC_ALPHA))

        let contentScaledRect = rect.applying(CGAffineTransform(scaleX: view.contentScaleFactor, y: view.contentScaleFactor))
        let scale = min(contentScaledRect.width / imageNativeSize.width, contentScaledRect.height / imageNativeSize.height)

        ciRawFilter.setValue(scale, forKey: kCIInputScaleFactorKey)

        var displayRect = CGRect(x:0, y:0, width:imageNativeSize.width, height:imageNativeSize.height).applying(CGAffineTransform(scaleX: scale, y: scale))
        displayRect.origin.x = (contentScaledRect.width - displayRect.width) / 2.0
        displayRect.origin.y = (contentScaledRect.height - displayRect.height) / 2.0

        guard let image = ciRawFilter.outputImage else { return }
        context.draw(image, in:displayRect, from:image.extent)
    }
}


あとは任意のフィルターをかけていきます。

// EV Filter
func exposureAdjusted(value: Double) {
    guard let ciRawFilter = ciRawFilter else { return }

    ciRawFilter.setValue(value, forKey: kCIInputEVKey)
    imageView.setNeedsDisplay()
}


// NeutralTemperature Filter
func temperatureAdjusted(value: Double) {
    guard let ciRawFilter = ciRawFilter else { return }

    ciRawFilter.setValue(value, forKey: kCIInputNeutralTemperatureKey)
    imageView.setNeedsDisplay()
}


// NeutralTint Filter
func tintAdjusted(value: Double) {
    guard let ciRawFilter = ciRawFilter else { return }

    ciRawFilter.setValue(sender.value, forKey: kCIInputNeutralTintKey)
    imageView.setNeedsDisplay()
}

Screen Shot 2016-09-13 at 9.14.19

まとめ

iPhone 6s / iPhone 6s Plus / iPhone SE / iPad Pro 9.7はリアカメラが1200万画素に向上しており、来るiPhone 7 Plusではデュアルカメラを搭載するなど、iOS搭載端末のカメラの高解像度化が一気に進んできました。
ユーザーも開発者も写真アプリをより一層楽しめる環境になりつつあるのではないでしょうか。

参考文献

Live Photo Editing and RAW Processing with Core Image
CoreImage Changes for Objective-C