半透明処理

概要:半透明処理を題材に、実践的なプログラムを作ります

半透明処理のアルゴリズムは簡単なので、
ちょっと実践的なプログラムを作ってみましょう。

■半透明処理

前景に不透明度(0〜100)が設定されている場合、背景の色と混ぜ合わさり、
最終的に表示される合成色は次の式で計算できます。

合成色=前景×不透明度+背景×(100−不透明度)

不透明度 0 なら 完全な透明、100 なら不透明です。
不透明度の範囲は 0〜100 が一般的ですが、0〜255 でも構いません。

更にその上に不透明度が設定されている画像がある場合、
その下の合成色を背景と見なせば、同じ計算式から合成色が求められます。
後はこの繰り返しです。

■素材画像を作る

プログラムの起動直後に素材となる画像を作成しておきます(またはファイルから読み込む)。
今回は DIB で作成しましょう。
これら素材画像に対して描画してはいけません。
また、直接表示する事もないので、BITMAPINFO 構造体は不要です。

作成するのは、
適当なグラデーションが描画された背景、
半透明処理を施す領域を含む前景、の二つです。

//背景
#define BW      640
#define BH      480
//前景
#define FW      500
#define FH      150

//DIB 背景  前景
static LPDWORD lpPixelB,lpPixelF;

//DIB作成
lpPixelB=(LPDWORD)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(BW*BH*4)+(FW*FH*4));
lpPixelF=lpPixelB+BW*BH;

前景は緑の枠を持つ、黒いウィンドウです。
ゲームの台詞表示に使われるアレと言えばわかるでしょう。
黒い部分のみ半透明処理を施します。

int x,y;
BYTE r,g,b;

//半透明にする色
#define ALPHA 0x00000000

//画像作成 背景
for(y=0;y<BH;y++){
    for(x=0;x<BW;x++){
        r=x%256; g=y%256; b=255-r;
        lpPixelB[x+y*BW]=(r<<16)+(g<<8)+b;
    }
}
//前景
for(y=0;y<FH;y++){
    for(x=0;x<FW;x++){
        lpPixelF[x+y*FW]=0x0000ff00;
    }
}
for(y=10;y<FH-10;y++){
    for(x=10;x<FW-10;x++){
        lpPixelF[x+y*FW]=ALPHA;
    }
}

■完成画像を作る

表示する際には、裏画面に素材画像を単純に並べたり、時に加工したりして、
完成画像を作ってから表画面に転送します。

その完成画像を作っていくビットマップを DIBSection で作成しましょう。
DDB では画素の色操作が困難ですし、DIB ではGDI関数が使えないからです。
大きさは背景画像に合わせます。

//DIBSection 完成画像
static BITMAPINFO bmpInfo;
static LPDWORD lpPixel;
static HBITMAP hBitmap;
static HDC hMemDC;

//DIB(Section)の情報を設定する 完成画像
bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth=BW;
bmpInfo.bmiHeader.biHeight=-BH;    //注意
bmpInfo.bmiHeader.biPlanes=1;
bmpInfo.bmiHeader.biBitCount=32;
bmpInfo.bmiHeader.biCompression=BI_RGB;
//DIBSection作成
hBitmap=CreateDIBSection(NULL,&bmpInfo,DIB_RGB_COLORS,(void**)&lpPixel,NULL,0);
hMemDC=CreateCompatibleDC(NULL);
SelectObject(hMemDC,hBitmap);

今回は前景を移動可能にしたり、
GDI関数による描画もあるので、DDB座標系に統一しておいた方が混乱がありません。
高さに負の値を指定してDDB座標系に統一している事に注目して下さい。

■GDI関数

GDI関数は文字列描画に使います。
文字列描画はよく行うので、完成画像にはDDBを使う事が多いのですが、
今回のようにピクセル操作まで必要な時はDIBSectionを使います。

//テキストの設定を変更する
SetTextColor(hMemDC,RGB(255,255,255));
SetBkMode(hMemDC,TRANSPARENT);

■前景の操作

移動はDDB座標系で考えます。
また、不透明度の変更も可能にします。

static BYTE a=50;             //不透明度(0〜100)
static POINT pt={100,100};    //表示座標

case WM_KEYDOWN:
    switch(LOWORD(wParam)){
        case VK_UP: pt.y-=10; break;
        case VK_DOWN: pt.y+=10; break;
        case VK_LEFT: pt.x-=10; break;
        case VK_RIGHT: pt.x+=10; break;
        case VK_ADD: if(a<100){a++;} break;
        case VK_SUBTRACT:if(a>0){a--;} break;
        default: return 0;
    }
    InvalidateRect(hWnd,NULL,FALSE);
    return 0;

■描画

最初に背景を完成画像に描画して、
完成画像を背景と見なして、前景との半透明合成を計算します。

半透明合成を施す領域は ALPHA 色の画素だけです。

バッファの参照領域にも注意して下さい。
存在しないバッファを参照するとメモリ参照エラーが発生します。
DDBはシステムが上手く処理してくれますが、DIBは自己管理が原則です。

RGBQUAD color,colorF;

//背景の描画
CopyMemory(lpPixel,lpPixelB,BW*BH*4);
//合成 前景×不透明度+完成画像×(100−不透明度)
for(y=0;y<FH;y++){
    if(pt.y+y<0 || pt.y+y>=BH) continue;
    for(x=0;x<FW;x++){
        if(pt.x+x<0 || pt.x+x>=BW) continue;

        if(lpPixelF[x+y*FW]==ALPHA){    //半透明処理
            CopyMemory(&colorF,&lpPixelF[x+y*FW],sizeof(RGBQUAD));
            CopyMemory(&color,&lpPixel[(pt.x+x)+(pt.y+y)*BW],sizeof(RGBQUAD));

            r=(BYTE)(colorF.rgbRed*a/100.0 + color.rgbRed*(100-a)/100.0);
            g=(BYTE)(colorF.rgbGreen*a/100.0 + color.rgbGreen*(100-a)/100.0);
            b=(BYTE)(colorF.rgbBlue*a/100.0 + color.rgbBlue*(100-a)/100.0);

            lpPixel[(pt.x+x)+(pt.y+y)*BW]=(r<<16)+(g<<8)+b;
        }else{          //前景そのまま
            lpPixel[(pt.x+x)+(pt.y+y)*BW]=lpPixelF[x+y*FW];
        }
    }
}

このように色で識別する他に、領域で識別する方法、マスクを使用する方法などが考えられます。
32ビットBMPなら、使われていない最上位バイトをマスクに使用すればいいでしょう。

後は文字列を裏画面に描画して、
完成した画像を表画面に転送するだけです。

char str[64];

//文字列描画
wsprintf(str,"現在の不透明度 %d %",a);
TextOut(hMemDC,pt.x+20,pt.y+20,str,(int)strlen(str));
//表画面へ転送
BitBlt(hdc,0,0,BW,BH,hMemDC,0,0,SRCCOPY);

★☆ ソースファイル表示 ☆★


戻る / ホーム