レイヤ合成(2)・合成モード

レイヤの合成モード
ペイントソフトのレイヤでは、レイヤの「合成モード」を指定できる場合があります。
レイヤの合成モードとは、そのレイヤをどういった方法 (計算) で合成するかの種類です。

前回やったレイヤ合成は、いわゆる「通常合成」です。
なんの特色もなく、普通に色を合成しました。

今回は、ペイントソフトでよく使われる合成モードをいくつか紹介します。

各合成モードの計算方法については、以下のサイトを参考にしてください。

スクリーンショット


2つの画像をレイヤとし、下は通常合成、上は指定モードで合成します。
左の項目から上のレイヤの合成モードを変更できます。
ソースコード
使用画像: bitmap2.bmp, circlegrad.bmp

012_blendmode.c
#include "sptk.h"

#define WIDTH  300
#define HEIGHT 220

SPTK_IMAGE *winimg;
SPTK_IMAGE32 *layerimg[2],*blendimg;
int blendmode = 0;

void blend_layer(SPTK_IMAGE32 *srcimg,int mode)
{
    SPTK_PIX_RGBA *psrc,*pdst,dst;
    int ix,iy,sr,sg,sb;
    
    psrc = srcimg->pixbuf;
    pdst = blendimg->pixbuf;
        
    for(iy = 0; iy < srcimg->h; iy++)
    {
        for(ix = 0; ix < srcimg->w; ix++)
        {
            dst = *pdst;
            
            sr = psrc->r;
            sg = psrc->g;
            sb = psrc->b;
            
            switch(mode)
            {
                /* normal */
                case 0:
                    break;
                /* add */
                case 1:
                    sr += dst.r; if(sr > 255) sr = 255;
                    sg += dst.g; if(sg > 255) sg = 255;
                    sb += dst.b; if(sb > 255) sb = 255;
                    break;
                /* sub */
                case 2:
                    sr = dst.r - sr; if(sr < 0) sr = 0;
                    sg = dst.g - sg; if(sg < 0) sg = 0;
                    sb = dst.b - sb; if(sb < 0) sb = 0;
                    break;
                /* mul */
                case 3:
                    sr = sr * dst.r / 255;
                    sg = sg * dst.g / 255;
                    sb = sb * dst.b / 255;
                    break;
                /* screen */
                case 4:
                    sr = sr + dst.r - sr * dst.r / 255;
                    sg = sg + dst.g - sg * dst.g / 255;
                    sb = sb + dst.b - sb * dst.b / 255;
                    break;
            }
        
            pdst->r = (sr - dst.r) * psrc->a / 255 + dst.r;
            pdst->g = (sg - dst.g) * psrc->a / 255 + dst.g;
            pdst->b = (sb - dst.b) * psrc->a / 255 + dst.b;
        
            psrc++;
            pdst++;
        }
    }
}

void draw()
{
    blend_layer(layerimg[0], 0);
    blend_layer(layerimg[1], blendmode);

    sptk_image32_blt_image(blendimg, 0, 0, 200, 200, winimg, 80, 10);
    sptk_update(NULL, 80, 10, 200, 200, 0);
}

void select_handle(SPTK_WIDGET *wg,int index)
{
    blendmode = index;
    draw();
}

int main()
{
    sptk_init("test", WIDTH, HEIGHT);
    
    winimg = sptk_window_get_image();
    
    layerimg[0] = sptk_image32_load_bitmap("bitmap2.bmp");
    if(!layerimg[0]) sptk_errexit("cannot load bitmap file");
    
    layerimg[1] = sptk_image32_load_bitmap("circlegrad.bmp");
    if(!layerimg[1])
    {
        sptk_image32_free(layerimg[0]);
        sptk_errexit("cannot load bitmap file");
    }
    
    blendimg = sptk_image32_create(200, 200);
    
    sptk_widget_select_create(0, 10,10, "NORMAL;ADD;SUB;MUL;SCREEN", select_handle);
    
    draw();
    
    sptk_run();
    
    sptk_image32_free(layerimg[0]);
    sptk_image32_free(layerimg[1]);
    sptk_image32_free(blendimg);

    return 0;
}
解説
今回は、画像を2つ使います。
"bitmap2.bmp" を背景用レイヤに、"circlegrad.bmp" のグラデーション画像を合成用レイヤとして使います。
項目選択ウィジェット
今回は、合成モードを選択する必要があるので、テキストを項目として選択できるウィジェットを使います。

sptk_widget_select_create() で、項目選択ウィジェットを作成します。

SPTK_WIDGET *sptk_widget_select_create(int id,
    int x,int y,const char *text,void (*handle)(SPTK_WIDGET *,int));

文字の描画には sptk_image_text() を使っているので、1 文字は 6x9 とサイズが決まっています。
そのため、ウィジェットの幅・高さは自動で計算されます。

text で項目のテキストをまとめて文字列として指定します。
複数項目は ';' で区切って指定します。

イベントハンドラは、選択項目が変更された時に呼ばれます。
第二引数に項目のインデックス番号 (0〜項目数-1) が渡されます。
複数レイヤの合成
レイヤが複数ある場合は、下のレイヤから順に合成結果用イメージに合成していきます。

今回は、layerimg[0] が下のレイヤ、layerimg[1] が上のレイヤのイメージです。
まず、下のレイヤを通常合成します。
次に、上のレイヤを選択されたモードで合成します。

ちなみに、レイヤの合成前に、前回やったような背景の描画を行っていませんが、今回は下レイヤがそのまま背景となるので、背景描画の処理は省いています。
共通の合成計算
まず、blend_layer() の最後の方を見てください。

pdst->r = (sr - dst.r) * psrc->a / 255 + dst.r;
pdst->g = (sg - dst.g) * psrc->a / 255 + dst.g;
pdst->b = (sb - dst.b) * psrc->a / 255 + dst.b;

前回やった合成計算が、すべての合成モードで共通して行われています。

合成計算はそれぞれの合成モードで異なるんじゃないかと思うかもしれませんが、
この計算に関しては、「合成元の色を、不透明度を元に合成先と合成する」ものなので、これは不透明度を適用するだけの計算だと思ってください。

実際には、合成元の RGB 値を各合成モードでそれぞれ変化させることで、合成モードが適用されるようになります。
各合成モードの計算
では、各合成モードではどういう計算が行われているか見てみましょう。

今回は、ペイントソフトでよく使われていて、計算も比較的簡単な
通常、加算、減算、乗算、スクリーン」 の5種類の合成モードを説明します。

まず、最終的な合成元の色を sr, sg, sb に格納させます。
この変数は必ず RGB の各値の範囲より大きいまたは小さい値を格納できる変数でなければなりません。
(つまり、今回の場合だと、8bit 符号なしの整数にしてはならない)
なぜなら、加算/減算の合成モードの計算途中において、255 以上の値や 0 以下の値になる場合があるからです。

以下は、各合成モードの特徴と計算式です。
SRC が合成元 (レイヤイメージ)、DST が合成先で、値が 0.0〜1.0 の範囲の場合。

通常通常合成。色に変化は与えない。
加算合成元と合成先の色を加算する。
合成元が白いほど色が明るくなる。黒で変化なし。
SRC + DST
減算合成先の色から合成元の色を減算する。
合成元が白いほど色が暗くなる。黒で変化なし。
DST - SRC
乗算合成元と合成先の色を乗算する。
いずれかの色が黒いほど色が暗くなる。白で変化なし。
SRC * DST
スクリーン合成先に合成元の色を上乗せする。
加算よりも色の変化が緩い。白で変化なし。
SRC + DST - SRC * DST

ここでは、RGB 値は 0〜255 の範囲なので、値を掛けた場合は 255 で割ります。
また、加算/減算時は、値が範囲外の場合は最小/最大値に調整する必要があります。

以上のように、合成元 (レイヤイメージ) の RGB 値と、合成先 (合成結果用イメージ) の RGB 値を、各合成モードの式で計算した後、その結果の RGB 値を合成元のアルファ値で合成先と合成して、不透明度を適用します。