« 袋文字の描画 | メイン | 止まらないウィジェットを作る »

2011年01月26日

Android:: 抜き色が倒せない

    

AvoidXfermode は、opColor を除いた場所に描画するか、opColor 上だけに描画します。みたいな説明になっている。
この説明を読むと一見カラーキー使えるのか?と思うんだけど、実は判定に使われるのは Destination color のみなので、抜き色指定に使えない。

関数はこんな感じ。
AvoidXfermode(int opColor, int tolerance, AvoidXfermode.Mode mode)

tolerance の意図するところがちょっとよくわからない。
tolerance は 0~255 の範囲で指定する(それ以外では例外発生)。

AvoidXfermode.Mode.AVOID は、Destination color が opColor 以外の時、描画する。
AvoidXfermode.Mode.TARGET は、Destination color が opColor の時描画する。

効果がわかりやすいように、以下にソースコードとその実行結果を

// 適当にしましまに塗る
mPaint.setColor(Color.GREEN);
canvas.drawRect(new Rect(0,  0,mCanvasWidth, 50), mPaint);
canvas.drawRect(new Rect(0,100,mCanvasWidth,150), mPaint);
canvas.drawRect(new Rect(0,200,mCanvasWidth,250), mPaint);
canvas.drawRect(new Rect(0,300,mCanvasWidth,350), mPaint);
canvas.drawRect(new Rect(0,400,mCanvasWidth,450), mPaint);

// それぞれのモードで矩形を描く
mPaint.setColor(Color.RED);
// 左にAVOIDで
mPaint.setXfermode( new AvoidXfermode( Color.GREEN, 255, AvoidXfermode.Mode.AVOID ) );
canvas.drawRect(new Rect(mCanvasWidth/8,0,mCanvasWidth/8+mCanvasWidth/4,mCanvasHeight), mPaint);

// 右にTARGETで
mPaint.setXfermode( new AvoidXfermode( Color.GREEN, 255, AvoidXfermode.Mode.TARGET) );
canvas.drawRect(new Rect(mCanvasWidth/2+mCanvasWidth/8,0,mCanvasWidth/2+mCanvasWidth/8+mCanvasWidth/4,mCanvasHeight), mPaint);
mPaint.setXfermode( null );

で、このソースコードで描かれる画像がこれ(半分のサイズにしている)。
avoidxfermodeimage.png
始めに塗った緑の上かそれ以外に描画されているのがわかると思う。

と言うことで、AvoidXfermode が抜き色には(1パスでは)使えないことがわかった。
ワーク用の Bitmap を準備して、そこにまず画像を描画後、次に AvoidXfermode を使用して抜き色に対して透明色を描画し、そのワーク画像を描画すれば期待の結果が得られると思うが、2回コピーすることになるのと、ワーク用のメモリが必要になるのが気になる。

PorterDuffColorFilter も試したんだけど、これはどうもブレンディングモード指定のようなもののようで、これも期待している用途には使えない。
ColorMatrixColorFilter は、近い結果を得られる。

ColorMatrixColorFilter は、ColorMatrix を色に掛け合わせてその結果を描画する。
マトリックスは 4x5。
マトリックスと演算式は以下のような感じ。
| a, b, c, d, e |
| f, g, h, i, j |
| k, l, m, n, o |
| p, q, r, s, t |
R' = a*R + b*G + c*B + d*A + e;
G' = f*R + g*G + h*B + i*A + j;
B' = k*R + l*G + m*B + n*A + o;
A' = p*R + q*G + r*B + s*A + t;
これを見ればどのような結果になるかすぐにわかると思う。
緑を透過色として扱いたければ、以下のようにすればそれなりに動く。
| 1, 0, 0, 0, 0 |
| 0, 1, 0, 0, 0 |
| 0, 0, 1, 0, 0 |
| 1, -1, 1, 0, 255 |

サンプルソースは以下のような感じ。

float matrix[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
 1.0f, -1.0f, 1.0f, 0.0f, 255.0f};
mPaint.setColorFilter( new ColorMatrixColorFilter( matrix ) );
canvas.drawBitmap( bitmap, x, y, mPaint );
mPaint.setColorFilter(null);

演算式を見ればすぐにわかるが、緑が入っていると透過されてしまうケースがあるので、この方法は不完全。
カラーマトリックスをしばらく考えたが、完全なものは無理だと思う。

結論、素直にアルファチャネルを使いましょう。
RGB565 などでとにかくメモリを節約したいのなら、AvoidXfermode で何とかならないこともない。
もっとうまくやる方法がありそうな気がするけど、調べた限りではわからなかった。
NDK 使って自前で実装とかなら出来るだろうけど。



投稿者 Takenori : 2011年01月26日 23:40




comments powered by Disqus
Total : Today : Yesterday : なかのひと