cockscomblog?

cockscomb on hatena blog

CoreImage.CIFilterBuiltins

iOSmacOSで画像を加工しようとした場合、Core Image frameworkを使うのが簡単だ。画像をハードウェアの支援で高速に処理でき、内蔵された様々なフィルターを使うこともできる。Mac OS X 10.4で初めて搭載されて以来、しっかりとアップデートされ続けている。

2019年のiOS 13やmacOS 10.15では、Core Imageの色々なフィルターをSwiftから扱いやすくなった。

CIFilter APIの変遷

最初期は以下のように、Key-Value Codingを活用したAPIだった。もともとCore Image frameworkは、GUIでユーザーに操作させることを想定しており、文字列などから動的に扱えるように作られている。(あるいは組み込みではないフィルターがインストールされている場合もあり、いずれにしてもKVCは有用である。)反面で、フィルター名やフィルターとパラメータの対応に気を配る必要がある。

import CoreImage

let image: CIImage = ...

guard let filter = CIFilter(name: "CIGaussianBlur") else {
    fatalError()
}
filter.setValue(20, forKey: kCIInputRadiusKey)
filter.setValue(image, forKey: kCIInputImageKey)
guard let processed = filter.value(forKey: kCIOutputImageKey) as? CIImage else {
    fatalError()
}

let context = CIContext()
context.createCGImage(processed, from: processed.extent)

iOS 8のタイミングで、KVCではないAPIも整備され、CIFilter.init(name:parameters:)CIImage.outputImageを使って、以下のように書けるようになった。

guard let filter = CIFilter(name: "CIGaussianBlur", parameters: [
    kCIInputRadiusKey: 20,
    kCIInputImageKey: image,
]) else {
    fatalError()
}
guard let processed = filter.outputImage else {
    fatalError()
}

さらにこれのショートカットで、CIImage.applyingFilter(_:parameters:)メソッドを使い、以下のようにも書ける。見た目はかなりよくなったが、ガウシアンぼかしのために"CIGaussianBlur"フィルターを使って、パラメータとしてkCIInputRadiusKeyを与える、ということは知らなければならない。

let processed = image.applyingFilter("CIGaussianBlur", parameters: [
    kCIInputRadiusKey: 20,
])

iOS 10の頃にはCIImage.applyingGaussianBlur(sigma:)メソッドなどが追加され、いくつかの基本的なフィルターが容易に利用可能になった。

let processed = image.applyingGaussianBlur(sigma: 20)

CIFilterBuiltins

iOS 13やmacOS 10.15から、新たにCIFilterにたくさんのクラスメソッドが追加された。このうちCIFilter.gaussianBlur()を使えば、これまでと同じ処理を以下のように書ける。

import CoreImage
import CoreImage.CIFilterBuiltins

let image: CIImage = ...

let filter = CIFilter.gaussianBlur()
filter.radius = 20
filter.inputImage = image
guard let processed = filter.outputImage else {
    fatalError()
}

let context = CIContext()
context.createCGImage(processed, from: processed.extent)

クラスメソッドのシグネチャclass func gaussianBlur() -> CIFilter & CIGaussianBlurになっているので、型を利用して簡単に扱えるようになっている。このようなクラスメソッドが、組み込みのフィルターそれぞれに用意されている。

import CoreImage.CIFIlterBuiltinsを忘れやすいので注意が必要だ。Core Imageのmodulemapをみると、以下のようになっている。

framework module CoreImage [extern_c] {
  umbrella header "CoreImage.h"
  export *
  module * { export * }
  
  explicit module CIFilterBuiltins {
      header "CIFilterBuiltins.h"
      export *
  }
}

まとめ

Core ImageAPIは徐々に改良が加えられている。

ユースケースに応じて異なるAPIを使うのがよいだろう。単純にガウシアンぼかしを掛けたいのであれば、CIImage.applyingGaussianBlur(sigma:)が最も簡単である。こういったショートカットが用意されていない組み込みのフィルターを利用するなら、CIFilterBuiltinsがよい。組み込みではないフィルターを利用するような場合は、必然的にCIImage.applyingFilter(_:parameters:)メソッドやKVCを使うことになる。