半透明処理のアルゴリズムは簡単なので、
ちょっと実践的なプログラムを作ってみましょう。
■半透明処理
前景に不透明度(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);
★☆ ソースファイル表示 ☆★