みなさんこんにちは。有山圭二です。

今年も、Google I/Oの季節がやってきました。

Google I/Oは、Google社がアメリカのサンフランシスコで開催する年次の開発者会議です。今年は5月28日と29日の二日間に予定されています。

基調講演ではGoogleの今後の方針や、新しいテクノロジー・プロダクトが発表されます。その他にも、各種テクノロジーを担当するGooglerに会って直に意見や要望を言えたり、I/Oに参加する世界中の開発者と意見が交換できるのも魅力の一つです。

今年のGoogle I/Oで何が発表されるのか、まだ不透明な部分が多いのが実際です。個人的には、Androidアプリ開発に使える新しい言語、例えばGolangやKotlinに正式対応するなどがあれば面白いなと思います。

ともあれ、この記事は引き続き、古くて新しいCanvasについてもっと知ろうと言う趣向でお送りします。

前回はCanvasの使い方全般を一通り解説したので、今回のテーマは予定通り"PorterDuff"です。

1.1 PorterDuffとは

PorterDuffは、２枚の画像を合成する際、それぞれのピクセルについてどのように処理するのか、操作の種類（Mode）ごとに実際の各ピクセルに対する処理内容を規定したものです。Canvasと同じく、AndroidのAPI Level 1の頃からあるとても古いAPIです。

PorterDuffを使うと、Canvasで画像や図形を描画する際、既に表示している画像の一部をくり抜いて下の表示内容を見えるようにしたり、四角い画像を角丸に整形したりできます。

なお、PorterDuffは、考案者であるThomas PorterとTom Duffのそれぞれの名前に由来します。

PorterDuffの使いどころ

Androidアプリを開発する上で、どのような場合にPorterDuffを使うのか。

例えば、カメラのプレビューにSurfaceViewをオーバーレイする際に「透明で塗りつぶす」と言う処理を実装したことはないでしょうか。

SurfaceViewで得られるCanvasは、前の描画がリセットされないので、必要に応じてCanvasを塗りつぶす必要があります。しかし、白色や黒色などで塗りつぶしてしまうと、SurfaceViewの下にあるカメラプレビューが見えなくなってしまいます。

この場合、PorterDuff.ModeのCLEARを使ってCanvasを透明に戻すという手法が一般的です（リスト1.1）。

リスト1.1: PorterDuff.Modeを指定している

canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

ここでは実は、PorterDuff.Modeの CLEAR を指定することで、描画色が有効な範囲（この場合はCanvas全体）を透明にする処理を実行しています。

なお、CanvasのdrawColorメソッドの第２引数を指定しないと、PorterDuff.Modeは SRC_OVER になります。つまり、ここで指定した色でキャンバスを塗りつぶすという意味になり、リスト1.1の場合、第２引数を省略するとCanvasは想定通りにはなりません。

その他にも、移動する枠で画像の一部を見せる。撮影した顔写真を用意したフレームの形に合うように切り出すなど、様々な場所でPorterDuffは活用できます。

Androidが用意しているPorterDuffのモードは、以下の通りです。

SRCとDST

PorterDuffは、２枚の画像を合成する処理する方法ですが、一部のモードでは２枚の画像をそれぞれ SRC と DST に分けて考えるものがあります。

例えば、前述のリストで括弧書きで(SRC/DST)としているものがそうです。

これらはPorterDuff.Modeの定数でそれぞれ、SRC_OVER, DST_OVERのように分けられます。

DSTはDestination。つまり合成の大元になる画像です。

一方、SRCはSource。DSTに対して合成する画像です。

例えば、画像Aと画像Bを合成する場合、PorterDuffMode.SRC_OVER(A, B)と、PorterDuffMode.SRC_OVER(B, A)で得られる画像は、それぞれ異なり、PorterDuffMode.SRC_OVER(A, B)とPorterDuffMode.DST_OVER(B, A)の結果は同じになります。

各モードの処理内容は、表1.1に示すとおりです 。

表1.1: PorterDuff.Mode. フラグ 処理内容 HW SW CLEAR [0, 0] ？ ？ SRC_OVER [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] ○ ○ DST_OVER [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] ○ ○ SRC_IN [Sa * Da, Sc * Da] ○ ○ DST_IN [Sa * Da, Sa * Dc] ○ ○ SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)] ○ ○ DST_OUT [Da * (1 - Sa), Dc * (1 - Sa)] ○ ○ SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc] ○ ○ DST_ATOP [Sa, Sa * Dc + Sc * (1 - Da)] ○ ○ XOR [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] ○ ○ DARKEN [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] × ○ LIGHTEN [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] × ○ ADD Saturate(S + D) ○ ○ MULTIPLY [Sa * Da, Sc * Dc] ○ ○ SCREEN [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] ○ ○ OVERLAY × ○

このテーブルだけ見ても筆者には何がどうなるかさっぱりわからないので、次からそれぞれのPorterDuffのモードについて、実例を挙げながら解説していきます。

1.3 PorterDuffモードの実例

SRCとDST

これから紹介する実例で、SRCとDSTそれぞれに指定する画像は図1.1に示す通りです。

今回は、SRCは青い丸。DSTは赤い十字の画像です。

それぞれ同じ大きさの画像で、黒枠内の格子模様は透明なピクセルを表します。

リスト1.2: PorterDuff.Modeの利用

int sc = canvas.saveLayer(x, y, x + W, y + H, paint, Canvas.MATRIX_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG ); // DST canvas.drawBitmap(mCross, 0, 0, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); // SRC canvas.drawBitmap(mCircle, 0, 0, paint); paint.setXfermode(null); canvas.restoreToCount(sc);

リスト1.2は、PorterDuff.Modeをつかって２枚の画像を合成するサンプルソースです。

setXfermode メソッドで、使用するPorterDuff.Modeを指定しています。

setXfermode メソッドでPorterDuff.Modeを指定する前に描画している内容がDST（今回の場合は赤い十字）、 setXfermode メソッドの後に描画する内容がSRC（青い丸）として合成されます。

SRC（Source）という名前から、理解が混乱するかも知れませんが、ここを間違えると後々大変なので、注意してください。

CLEAR

CLEARは、DSTのピクセルを削除する（透明にする）モードです。

なお、PorterDuffのモードCLEARは、ハードウェア・アクセラレーションが有効の状態と無効な状態で挙動が異なるという現象が、筆者の手元で確認されています。

仕様上は、ハードウェア・アクセラレーションが無効な状態が正しい（ピクセルを全て透明にする）のですが、なぜこのような違いが発生しているのかについては、調査中です。

Android 4.0(API Level 14)以上では、ハードウェア・アクセラレーションは標準で有効になっている ます。

もし、ハードウェア・アクセラレーションを無効にしたい場合は、リスト1.3に示すとおり、個々のViewについて設定できます。

リスト1.3: ViewのコンストラクタでHardwareAccelerationを明示的に無効化する

setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);

OVERは、２枚の画像のどちらかをもう一方の上に重ねる（Over）モードです。

DST_OVERとSRC_OVERがあり、DSTとSRCどちらを上にするかを指定します。

INは、２枚の画像の重なる部分だけを残してあとは透明にするモード（交差）です。

DST_INとSRC_INがあり、どちらの画像を主体にするのかを指定します。

OUTは、一方の画像からもう一方の画像の重なる部分を透明にするモード（型抜き）です。OUTを指定して全面塗りつぶしをすることで、CLEARと同じ働きをします。

DST_INとSRC_INがあり、どちらの画像を主体にするのかを指定します。

ATOPは、一方の画像の上にもう一方の画像のIN（交差）の結果を重ね合わせるモードです。

DST_ATOPとSRC_ATOPがあり、どちらの画像を主体にするのかを指定します。

XOR

XORは、２枚の画像の重なる部分だけを透明にするモード（排他的論理和）です。

SRC_OUTとDST_OUTを重ねた結果になります。

ADD

ADDは、２枚の画像のそれぞれのピクセルの値を加算するモードです。

その他

PorterDuff.Modeは他にもDARKEN, LIGHTEN, MULTIPLY, SCREEN, OVERLAYがありますが、筆者の経験上、利用頻度が余り高くないのでここでは説明を省略します。

DARKENとLIGHTENは、日光や陰のような色味を表現するには適しているのですが、ハードウェア・アクセラレーションはこれらのモードをサポートしません （図1.14, 図1.15）。OVERLAYも同様です。

どうしてもこれらのモードを使いたい場合は、リスト1.4のように、Viewで個別にハードウェア・アクセラレーションを無効にすることで機能します。

リスト1.4: ViewのコンストラクタでHardwareAccelerationを明示的に無効化

setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);

しかし、ハードウェア・アクセラレーションを無効にした場合、描画パフォーマンスが低下します。

また、CLEARモードの挙動が変わる（図1.16）という現象が、筆者の環境で発生しています。

本来の仕様的にはハードウェア・アクセラレーションが無効になっているときの動作が正しいようで、有効時の動作はSRC_OUTやDST_OUTと同じになっています。

CLEARを使わなくても同様のことは実現可能（OUTを使ってピクセル削除）であること、パフォーマンスの低下に見合うメリットがあるのか、ハードウェア・アクセラレーションの無効化は慎重に検討してください。

1.4 まとめ

いかがでしたか？

今回は、不透明度（アルファ値）が全て最大値（完全に不透明）の画像を扱いましたが、透明度のある画像同士の合成が出来るようになれば、さらに強力な味方になることは間違いありません。

PorterDuffは、あまり目立たない機能です。

しかし、ひとたび使いこなせれば複雑な画像素材を何枚も用意しなくても、一つのカスタムビューで柔軟な表示ができるようになる、大変便利な機能です。

是非、試してみてください。