shader入門 -CIKernelでカスタムフィルター作成-

こんにちは、Pairs事業部エンジニアの川端です。
この記事はEureka Advent Calendar 2016 2日目の記事です。
初日はkagaさんのエウレカに転職してみたぞ!でした!
 
今回は、Core Imageをつかったカスタムフィルター作成について書きたいと思います。
 

画像加工とCore Image

画像加工をアプリで行ったことはありますか?
画像の色味を変えることや、画像全体をぼかしたりしたことはありますか?
 

これらはどのような処理で実現されているのでしょうか。
 

iOSでは、Core Imageというフレームワークを利用することで、このような処理を簡単に実現できます。
 

Core ImageにはiOS10のSDKでは、なんと180ものフィルターが入っています。フィルターとは、色調変化や変形などの加工を施す機能のことです。
 

ここから、最新のSDKで使えるフィルターの一覧が確認できます。ブラー(ぼかし)やディストーション(歪み)など、様々なフィルターがあることが確認できます。
 

フィルターの使い方について

実際の実装の過程を追ってみましょう。
 

画像加工アプリでは以下のような過程を経て、画像加工が行われます。process_in_core_image
UIImageはCIFilterに入力する際に、CIImageに変換して取り込まれ、フィルター処理後、CIImageが出力されます。さらにUIImageに変換し、UIImageViewといったViewに反映されることになります。
 

たとえば、以下は与えられた画像をセピア風に加工するコードです。
 

let image = UIImage(named:"original.png")
let inputImage = CIImage(image: image)
let filter = CIFilter(name: "CISepiaTone", withInputParameters: ["inputIntensity": 1.0])
filter?.setValue(inputImage, forKey: "inputImage")

if let outputImage = filter?.outputImage {

  let ciContext = CIContext(options: nil)
  let cgImage = ciContext.createCGImage(outputImage, from: inputImage!.extent)
  return UIImage(cgImage: cgImage!)
}

先ほどの図のように、 UIImage から CIImage を作り、 CIFilter を作成して通してCGImage を作成し UIImage を作っているのがわかります。
 

この処理でフィルターに与えられているのは「 CISepiaTone 」というフィルターの指定と、「 inputIntensity 」というフィルター処理に必要な数値のみです。

 

上の実行前と実行後

shader_apply_sepia
 

たしかに画像がセピアになることが確認できますよね。簡単に画像加工処理を実装できるのは、Core Imageの素晴らしいところです。このように、フィルターを使う分には中身の処理を知る必要はありません。
 
ですが、今回は、そこを少し掘り下げて考えてみましょう。

フィルターは何をやってるのか

CIFilter は、その中でどんなことが行われているのでしょうか。具体的には、 shader と呼ばれる処理が行われているのです。

shaderとは

shaderとはなんでしょうか。

「shader」は 頂点色やピクセル色などを次々に変化させるもの (Wikipedia)

わかるようなわからないような説明ですね。
 

どういったものをshaderと言うのかみていただくために下のURLを開いてみてください。
js4k intro – GLSL editor

時間とともに画面の色が変化していますよね。これがshaderです。

shader とは、画面のある座標が与えられた時、その座標の色を指定したり、座標を変更するプログラム です。
 

このshaderはGLSLという言語で書かれています。一見、main関数があったりしてC言語のようです。GLSLの話が出たのは、Core Imageとは無関係ではありません。カスタムフィルタはCIKernelというものをベースに作ります。

 

そのCIKernelはCore Image Kernel Language (CIKL)で書かれています。
CIKLはGLSLの方言(dialect)にあたります。画像加工に必要なCIKernelを書くためにはGLSLで雰囲気をつかむことが近道になります。

 

GLSLの文法や、何ができるかということは試行錯誤して学んでいくのが良いでしょう。
 

具体的なGLSLの勉強はコチラのシリーズがおすすめです。
また、GLSLで様々な表現ができることは、 Shadertoyを見てみるとわかると思います。Shadertoyでは、GLSLを使って作られた様々な作品を見ることができます。たった座標と色の情報だけで奥深い表現ができるのはすごいですよね。

CIKernelでカスタムフィルタを作ってみる

少し話が深くなりすぎましたが、 CIKernel ではGLSLのような表記を用いて自分だけのカスタムフィルタを作ることができます。
 
CIKernelクラスは二つに分かれます。
CIWarpKenelCIColorKernel です。

cikernel_class_relation

それぞれに役割があります。
 

入力された画像の色を変えたいなら CIColorKernel を用いて適応し、ピクセルの位置を変えたいならCIWarpKernel を用いて適応する、というように使い分けます。

 
たとえば、画面を歪ませて、色味を加える、といった処理を行いたければ複数のCIKernelを繋げて(chain)処理を行うようなイメージになります。
process_in_core_image_chaining

それぞれのCIKernelには戻り値のルールがある

CIWarpFilter の戻り値はvec2 です。

元の座標( vec2 destCoord() )をどう移動させるかという処理を書くので戻り値は入力値を形式が同じです。CIColorFilter の戻り値はvec4 です。ある座標での色を決めるためのものなのでRGBAの vec4(r,g,b,a) となります。

カーネル内部で扱うことのできる関数(Appleドキュメントより)

// 比較系

compare

// 三角関数系

cos_
cossin
cossin_
sin_
sincos
sincos_
tan_

// 入力された座標値

destCoord

// サンプラーに関するもの

sample
samplerCoord
samplerExtent
samplerOrigin
samplerSize
samplerTransform

// アルファコンポネントに関わるもの

premultiply
unpremultiply

CIWarpKernel で簡単に、画像のxとyを入れ替えるものを作ってみましょう。

kernel vec2 swapXandY() {
  return vec2(destCoord().y, destCoord().x);
}

実行前と実行後

shader_apply_swap
確かに、入力されたxとyが入れ替わった状態なのが確認できます。
 

CIColorKernelでも何か試してみます。
CIColorKernel で簡単に、入力の緑を青と交換して返すようにしてみましょう

kernel vec2 SwapGreenAndBlue(__sample s) {
  return s.rbga;
}

実行前と実行後

shader_apply_color
若干青みががったのがわかりますね。引数の__sampleは、画像の色情報です。もちろんCIColorKernel内で座標を取得することも可能です。画像の半分だけこの処理をするシェーダーだと次のようになります。

kernel vec4 swapGreenAndBlueWhenXislowerThanHalfWidth ( __sample s, float imageWidth)
{
  vec2 dest = destCoord();
  if(imageWidth/2.0 > dest.x) {
    return s.rbga;
  }
  return s.rgba;
}

実行前と実行後

shader_apply_color_half
画像の左半分だけ色が変わっているのが確認できましたね。この処理の場合だと、外から画像のサイズを渡しています。外から呼ぶコードはGithubより確認ください。CIFilterで自分だけのカスタムフィルタを作れば、いろんなことができそうですね。関数を使ったり、フィルターを重ねて見たりして遊んで見てはいかがでしょうか。
 

今回のモノクロ調・セピア調・カスタムを含んだサンプルは コチラから確認できます。興味があればクローンして実行してみてください。

その先へ

CIKernelでカスタムフィルタができるようになったのはiOS8からです。
iOS10では、Live Photoにフィルタを当てたり、RAW画像に対して加工ができるため、
撮ったあとから光量の設定を変えたり、複雑なことができるようになりました。
 

興味があれば、少しずつ、shaderの書き方にも親しんでいき、最後に新しいOSでできるようなったことにチャレンジしていくのがよいでしょう。
 

新しいiOSでできることをキャッチアップするにはWWDCの動画がおすすめです。
WWDCで発表された画像編集関連の動画を2つ掲載しておきます。
Live Photo Editing and RAW Processing with Core Image/WWDC 2016
Developing Core Image Filters for iOS/WWDC 2014

明日は@muukiiさんのCarthageのcopy-frameworkをスキップして開発時のビルドを高速化するです。お楽しみに!

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

Angular2は「使える」フレームワークか?

AngularJSでつらくならないために抑えておきたい5つのポイント