アルファ値を考慮したレイヤー合成

最近新しいPhotoshopを手に入れて、少し浮かれているお出汁orいりもとです。

さて、Photoshopをはじめレイヤー機能を持つ画像処理ソフトには、ほぼ必ずブレンドモードの設定があります。
これは上下レイヤーの色情報を「合成」して別の色を作り出す処理で、Photoshopで言えば各レイヤーの「乗算」「スクリーン」といったものがこれに当たります。
以下は有名なLennaの画像を少し細工したものですが、上に乗っている緑色がブレンドモードごとにどう作用するかの簡単な例です。

Photoshopの各ブレンドモードについては、以下のサイトが詳しい。

osakana.factory - ブレンドモード詳説

ただ、実際のレイヤー合成の計算は、これらの計算式だけではないのです。

どういうことかと言うと、これらの式には不透明度(アルファ)が考慮されていないんですね。
つまり、完全な不透明(アルファ最大)の画像に関しては上記で解説されている通りでいいのですが、少しでも透明な画像を乗せた場合にはうまく計算できないわけです。

というわけで今回は、レイヤーのアルファまで考慮した合成について書きます。

なお、記事では背景を下のレイヤー、前景を上のレイヤーと定義して使うことにします。
また次の記号を使います。

  • C_b: 背景色
  • C_f: 前景色
  • C_r: 背景色と前景色をブレンドモードに従って合成した色(osakana.factoryさん参照)
  • \alpha_b: 背景のアルファ
  • \alpha_f: 前景のアルファ

それぞれ定義域は[0,1]に正規化しているものとするので、適宜読み替えてください。

前景のアルファを考慮する

まずは背景が完全に不透明(\alpha_b = 1)で、その上に適当なアルファを持った前景を重ねることを考えます。
これは非常に単純で、\alpha_fを使ってC_bC_rの加重平均を取るだけで済みます。
つまり計算結果の色Cは、

C = \alpha_f C_r + (1 - \alpha_f) C_b

となります。以下は乗算について、前景のアルファのみを変化させた場合を並べたものです。

この計算式はいくつかの解説書やサイトにも掲載されているので、すでに知っている方も多いと思います。実際、背景が常に不透明であることが分かっていれば、事実上この式で問題ありません。
ところが\alpha_bを最大値と見なしている以上、Photoshopのように背景が不透明とは限らない場合の合成には使うことができません。

前景、背景両方のアルファを考慮する

というわけで、前景に加えて背景のアルファも考慮に入れます。
しかし背景のアルファを考慮に入れるとしても、一体どういう式になるのかがよく分からない。
そこで、実際のPhotoshopの動作を見てみると、以下のようになっています。

つまり、背景のアルファが0に近づくにつれて、前景色そのものが直接現れてくるわけですね。
(※背景と一緒に薄くなったり、チェッカーボードに合成されたりはしません。ここを勘違いされている方が多いような気がしたので、特に強調しておきます)

これをもとに合成結果の特徴を列挙すると、

前景のアルファ 背景のアルファ 合成結果
100% 100% C_r
100% 0% C_f
0% 100% C_b
0% 0% 0(透明)

となります。
これを満たすような式を2個のアルファから作ってやればいいわけです。

ここで少し話は変わりますが、透明なもの同士を合成する以上、結果もある程度透明であることは想像が付きます。
つまり、合成結果のアルファも前景と背景から計算してやる必要があるわけです。
\alpha_f\alpha_bから合成結果のアルファ\alphaを求めるには、以下の式を使います。

\alpha = 1 - (1 - \alpha_f) (1 - \alpha_b)

1から\alpha_fおよび\alpha_bを引いたものは、レイヤーの透明度と考えられます。式の意味的には、一度透明度に変換したものを乗算して、その結果を1から再び引いてアルファに戻していることになります。
具体的な数値だと、アルファ50%(透明度50%)のピクセル2個の合成はアルファ75%(透明度25%)になる、といった感じです。
(ちなみに、これはスクリーン合成と同じ式だったりします)

さて、この式を変形すると、以下のようになります。

 \alpha = \alpha_f \alpha_b + \alpha_f (1 - \alpha_b) + (1 - \alpha_f) \alpha_b
(過程略)

このように無理やり3つの項に分けたのですが、このようにすると、それぞれの項がちょうど色の重みを表すようになります。つまり、

重み
C_r \alpha_f \alpha_b
C_f \alpha_f (1 - \alpha_b)
C_b (1 - \alpha_f) \alpha_b

という対応関係が成り立つわけです。

式だけではよく分からないので、それぞれの項を\alpha_f\alpha_bについてプロットしたものを見てみます。


これを見ると、さきほど列挙した合成結果の特徴(図の4隅に該当する)にぴったり一致していることが分かると思います。
また\alpha_b = 1とすると、先ほど書いた加重平均そのものになります。

というわけで、最終的な色Cとアルファ\alphaの式は、

\alpha_1 = \alpha_f \alpha_b

\alpha_2 = \alpha_f (1 - \alpha_b)

\alpha_3 = (1 - \alpha_f) \alpha_b

として、以下のようになります。

\alpha = \alpha_1 + \alpha_2 + \alpha_3

C = \left\{\alpha_1 C_r + \alpha_2 C_f + \alpha_3 C_b\right\} \div \alpha

ここで注意したいのは、単純にC_rC_fC_bそれぞれに重みを掛けて足しただけでは、正確な色にはならない点。\alpha_1\alpha_2\alpha_3の作用で色自体が「薄まって」いるので、\alphaで割って補正してやる必要があるのです。
また\alpha=0のときは除算ができないので、C=0などと例外処理してやります。

かなり天下り的な説明になりましたが、以上がPhotoshopなどで用いられる合成の式となります。
このままでは計算回数が多いので、実際はもっと式全体を最適化したものが使われているのではないでしょうか。