画像のアルファブレンドを行うときに陥りがちな罠


まず、アルファブレンドについておさらい。

アルファブレンドは以下の式で行う。
dst = a * src1 + (1 - a) * src2
    a:重み[0..1]

2枚の入力画像に対応する画素値src1とsrc2に対する単純な重み付き平均の式だが、
重みaをsrc1のアルファ値にすることで、src1を前景画像、src2を背景画像とする合成処理を行うことができる。(dst=src2)


 

unsigned char Fg[imageSize];
unsigned char Bg[imageSize];
for ( int i = 0; i < imageSize; i += 4 )
{
    unsigned char a = Fg[i + 3];   // αチャンネル.
    Bg[i + 0] = (Fg[i + 0] * a + Bg[i + 0] * (255 - a)) / 255;   // Bチャンネル.
    Bg[i + 1] = (Fg[i + 1] * a + Bg[i + 1] * (255 - a)) / 255;   // Gチャンネル.
    Bg[i + 2] = (Fg[i + 2] * a + Bg[i + 2] * (255 - a)) / 255;   // Rチャンネル.
}

ふつうじゃこんな書き方はしない(というよりありえない)けど、ようはこんなかんじ。

ここで、
こういうシチュエーションがたまにあったりする。
1.前景画像が複数ある。
2.かつ、前景画像は変わらないが背景画像を場合応じて変えたい。

べつに上のような状況でも、背景画像にどんどんアルファブレンディングしていったらいいのだけども、
背景画像が変わるたびにそれらを都度行うのはあまりにも効率が悪い。
(サイズが大きかったり、数が多かったりするともはや無視できるコストじゃなくなる)

というわけで、背景画像と同じサイズのレイヤーを1つ用意しておき、
複数の前景画像の合成をレイヤーに対して先に行なっておくことで、
あとは、背景画像を変えるタイミングではレイヤーを1回だけ合成すればすむようになる。

じゃあそれでいいじゃん。
しかしそうは問屋が下ろさない。

実際やってみましょう。


の3つの前景画像を用意。
(マスクにあたるアルファ値は0xA4)

まず、レイヤーを介さずに通常通りにアルファブレンドを行った場合。


 
次に、レイヤーを介した場合。


 
結果が違うことがおわかりでしょうか。

なにが悪いのかは明白で、レイヤーへの合成をアルファブレンドで行なっているから。
そのため、レイヤーに合成した時点で、レイヤーの色とアルファブレンディングされてしまう。
(今回はレイヤーを(0xff, 0xff, 0xff)にしてたので、白っぽくなっちゃったわけ)

やりたいことは、
・レイヤーとのアルファブレンドはしない。
・しかし、レイヤー上ですでに合成された画素とのアルファブレンドは行う。
の2点。

これらを考慮したのが、以下のコード。

unsigned char Fg[imageSize];
unsigned char Bg[imageSize];
for ( int i = 0; i < imageSize; i += 4 )
{
    unsigned char fa = Fg[i + 3];   // 前景画像のαチャンネル.
    unsigned char ba = Bg[i + 3];   // 背景画像のαチャンネル.
    unsigned char a = (fa + ba) - fa * ba / 255;    // 合成後のαチャンネル.

    Bg[i + 0] = (Fg[i + 0] * fa + Bg[i + 0] * (255 - fa) * ba / 255) / a;   // Bチャンネル.
    Bg[i + 1] = (Fg[i + 1] * fa + Bg[i + 1] * (255 - fa) * ba / 255) / a;   // Gチャンネル.
    Bg[i + 2] = (Fg[i + 2] * fa + Bg[i + 2] * (255 - fa) * ba / 255) / a;   // Rチャンネル.
}

 
重み付けを、前景画像の重みと背景画像(この場合はレイヤー)の重みの両方を加味して行なっている。

レイヤーへの合成を、通常のアルファブレンドではなく、上の合成で行うことで、
最終背景画像とレイヤーとの合成をアルファブレンドで行なっても、結果が異なることがなくなる。


ただ、上のコードは不具合と問題点がある。
不具合の方は一目瞭然だけど。
あともう一つは最終結果。かなり簡略化したので、正確にはレイヤーを介したときと
そうでないときとで結果が完全に一致しないときがある。
(画素によって、1程度の誤差が出る)

けどまあ、こだわりだすとここに書けるレベルの話じゃなくなってくるし。
(量が多くなってめんどくさいって意味でね)