Quartz Composerにどっぷり!
Core Image Kernel パッチで自前のエフェクトを作る

今回のテーマ

今回のテーマは、Core Image Kernel パッチで自前のエフェクトを作ること。

推奨環境 この解説は、以下の環境を前提に作成し、動作確認等を行っています。ご確認ください。

改版履歴

自前のエフェクトは作れるの?

Quartz Composer には、様々な画像に対するエフェクトがありますが「こんなエフェクトあったらいいのに」なんてことがあるかもしれません。そんな夢を叶えてくれるのが Core Image Kernel パッチです。これを使えば、自前のパッチを作ることが可能です。

Core Image Kernel パッチを配置して Inspector で Settings を見ると、なにやらプログラムらしきものが書かれています。これは、OpenGL Shading Language と呼ばれる言語です。C 言語に似た文法を持っていますので、C 言語の経験のある方なら若干の知識だけでプログラミング可能です。OpenGL のオフィシャルページに PDF があります。ただし、Core Image Kernel パッチでは、OpenGL Shading Language の全ての機能が使えるわけではありませんので注意が必要です。

 → OpenGL Shading Language

逆に、Mac OS X の機能である Core Image Kernel Language 拡張機能を使うことが出来ます。

 → Core Image Kernel Language

Core Image Kernel パッチは、入力と出力に Image がありますので、ここで処理する画像の入力と処理した画像の出力を行います。入力ポートには color がありますが、これはあくまでもデフォルトで、好きなものを入力ポートに追加してプログラムのパラメータに渡すことが出来ます。

デフォルトの Core Image Kernel を解析

まずは、Core Image Kernel パッチにデフォルトで入っているプログラムがどのように動いているかを見ていきます。

multiplyEffect という関数があって、わずか 1 行のプログラムが書いているだけです。これだけでフィルターになるわけですが、これは、全てのピクセルに対してこの関数が呼び出されて、出力するピクセルの色を関数の戻り値として返しているためです。返り値の型は、vec4 となっていますが、これは、浮動小数値の float を 4 つ集めたものです。つまり、色情報ですので、RGB の 3 原色と透明度情報を含めた RGBA を返しているわけです。

続いて引数を見ていきます。引数の書き方も C 言語とほぼ同じで、変数の型と引数の名前を順に書いて、コンマで区切って繰り返しです。sampler image というのがありますが、sampler というのは画像データのことで入力画像データがこれで参照できます。__color color は、色情報を表す __color という型の値を入力しています。

そして、プログラムの処理の中身ですが、sample という関数と color を掛けたものを返しています。

return sample( image, samplerCoord( image ) ) * color;

sample という関数は内蔵の関数で、指定画像の指定ピクセルの色情報 ( vec4 ) を返す関数です。第 1 引数で画像を、第 2 引数で座標を指定します。

色情報 = sample( 画像データ, 座標 );

samplerCoord という関数も内蔵の関数で、画像を与えると現在処理中の座標が取得できます。座標の情報は、float を 2 つ集めた vec2 という型になっています。もし最後の「 * color 」の部分がなければ、入力画像と出力画像が全く同じになります。color を掛けることで各色の成分を変えて出力することが出来ます。vec4 同士のかけ算というのは、それぞれの成分同士でのかけ算になります ( 他の演算子でも一般に同じです )。つまり、

color3 = color1 * color2;

という式は、下の式と等価です。「 .r 」は赤の成分、「 .g 」は緑の成分、「 .b 」は青の成分、「 .a 」は透明度の成分を表します。

color3.r = color1.r * color2.r;
color3.g = color1.g * color2.g;
color3.b = color1.b * color2.b;
color3.a = color1.a * color2.a;

色の情報は、RGBA それぞれ 0.0 〜 1.0 の値を持ちますので、色情報と色情報を掛け合わせるとほとんどの場合は 0.0 に近づきますので暗くなります ( 透明度の成分は透明度が増します )。ということで、Core Image Kernel パッチのデフォルトのプログラムは、「 入力画像をより暗く、より透明度を上げる方向への色変換を行う 」ものになっていることがわかります。

簡単なエフェクトを作ってみよう

サンプルとして、いくつか簡単な画像エフェクトを作ってみましょう。最初は、画像の反転です。

kernel vec4 invert( sampler image )
{

 vec4 src = sample( image, samplerCoord( image ) );
 vec4 dst = 1.0 - src; /* Invert RGBA */

 dst.a = src.a; /* Keep Alpha */

 return( dst );

}

RGB については、1.0 から元の色を引くことで反転になりますが、透明度の部分はそのままにしたいので、別途処理しています。

次は、ぼかしです。現在のピクセルの上下左右 4 つのピクセルと自分自身の色を足して 5 で割ります。sample 関数に渡す座標を上下左右にずらしてあげなければいけませんが、「 vec2( a, b ) 」のようになっているところがあります。これは、vec2 型の数値を作るための関数です。

kernel vec4 blur( sampler image )
{
 vec2 xy = samplerCoord( image );

 vec4 p = sample( image, xy );
 vec4 pUp = sample( image, xy + vec2( 0, -1 ) );
 vec4 pLeft = sample( image, xy + vec2( -1, 0 ) );
 vec4 pRight = sample( image, xy + vec2( 1, 0 ) );
 vec4 pLow = sample( image, xy + vec2( 0, 1 ) );

 return ( p + pUp + pLeft + pRight + pLow ) / 5.0;
}

最後は、クロマキー用フィルタです。青に近い色を透明色に置き換えます。

kernel vec4 multiplyEffect( sampler image )
{

 vec4 src = sample( image, samplerCoord( image ) );
 vec4 dst = src;

 dst.a = ( 1.0 < ( src.b + 0.3 ) * sign( src.r + src.g - 0.2 ) ) ? 0.0 : src.a;

 return( dst );

}

青に近いかどうかの判定を「 青の成分が 0.7 より大きく、かつ、赤と緑の成分を足して 0.2 より小さいという条件を満たしたとき 」としました。満たした時は、透明度を 0.0、満たしていない時は透明度は元画像の透明度を使うようにしています。一般的には、これを表現するのには、 if 文を使ったりしますが、残念ながら Core Image Kernel パッチでは、以下のような if 文も && 演算子もサポートされていません。

 if (( 0.7 < src.b )&&( ( src.r + src.g ) < 0.2 )) dst.a = 0.0;
 else dst.a = src.a;

ということで、条件分岐のあるものはなかなか作りづらいですが、うまく数学的な処理をするしかありません。