・半透明処理をしてみよう (99/2/17)

アドベンチャーゲームなどではメッセージウィンドウが半透明になっていて 下の絵が透けているという画面が良く見られます。Direct X を使えば 簡単に出来そうですが(と言っていも Direct X は良く知らないので 出来るかどうかしりませんが)、GDI の場合はそんな API は無いので 自分でルーチンを書かなければなりません。

半透明処理の基本的な考え方は、元の絵とマスクしたい色を足して平均を 取るということです。つまり、元の絵のある座標(x,y) の点の色が RGB(r,g,b) で、マスクしたい色が RGB(R,G,B) だとしたら、半透明処理を した後の点(x,y) の色は RGB((r+R)/2,(g+G)/2,(b+B)/2) となります。

まあ、詳しくは下のソースをみて下さい。 CreateSemiTransBmp() が実際に半透明処理を行っている関数で、元の ビットマップ(hSrcBitmap) の矩形範囲(x,y)-(x+FWIDTH,y+FHEIGHT) に 対して半透明処理をして 24bitDIB(hDib)に保存しています。結果は こんなふうになります。


// 半透明処理サンプル 99/2/17
// (C) tokai@fides.dti.ne.jp

#include < windows.h>

#define WINWIDTH 480 // ウィンドウの幅
#define WINHEIGHT 180 // ウィンドウの高さ
#define FWIDTH 100 // 枠の幅
#define FHEIGHT 50 // 枠の高さ

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
VOID CreateSemiTransBmp(HDC,HBITMAP,HBITMAP,LPBYTE,LONG,LONG,RGBQUAD);

char szClassName[] = "HANTOMEI";

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreInst,
				   LPSTR lpszCmdLine,int nCmdShow)
{
    HWND hWnd;
    MSG lpMsg;
    WNDCLASS wndClass;
	
	// ウィンドウ登録
    if (!hPreInst) {
        wndClass.style=CS_HREDRAW|CS_VREDRAW;
        wndClass.lpfnWndProc=WndProc;
        wndClass.cbClsExtra=0;
        wndClass.cbWndExtra=0;
        wndClass.hInstance=hInst;
		wndClass.hIcon= NULL;
        wndClass.hCursor=LoadCursor(NULL, IDC_ARROW);
        wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
        wndClass.lpszMenuName=NULL;
        wndClass.lpszClassName=szClassName;
        if (!RegisterClass(&wndClass)) return FALSE;
    }
	
	// ウィンドウ作成
    hWnd = CreateWindow(szClassName,
        "半透明",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
		WINWIDTH,WINHEIGHT,NULL,NULL,hInst,NULL);
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
	
	// メッセージループ
    while (GetMessage(&lpMsg, NULL, 0, 0)) {
        TranslateMessage(&lpMsg);
        DispatchMessage(&lpMsg);
    }
	
    return (lpMsg.wParam);
}


// プロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	static HINSTANCE hInst;
	HDC hDC,hMemDC;
	RECT rt;
	static HDC hBufDC; 
    static HBITMAP hBufBit,hBgBitmap,hDib;
	static LPBYTE lpDibBuf,lpDibData;
	static LPBITMAPINFO lpBitInfo;
	static RGBQUAD rgbMask;
	CHAR szStr[256];
	
    switch (msg) {
		
	case WM_CREATE: // 初期化	
		
		// インスタンス取得
		hInst = (HINSTANCE)GetWindowLong(hWnd,GWL_HINSTANCE); 
		
		// ビットマップロード(test.bmp)
		hBgBitmap = (HBITMAP)LoadImage(hInst,"test.bmp",
			IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

		// 裏画面の作成
		GetClientRect(hWnd,&rt);
		hDC = GetDC(hWnd);
		hBufBit = CreateCompatibleBitmap(hDC,rt.right - rt.left,
			rt.bottom - rt.top);
		hBufDC = CreateCompatibleDC(hDC);
		SelectObject(hBufDC,hBufBit);
		ReleaseDC(hWnd,hDC);
		
		// ビットマップ貼り付け
		hMemDC = CreateCompatibleDC(hBufDC);
		SelectObject(hMemDC,hBgBitmap);
		BitBlt(hBufDC,0,0,rt.right - rt.left,rt.bottom - rt.top,
			hMemDC,0,0,SRCCOPY);
		DeleteDC(hMemDC);
		
		// 24bit DIB 作成

		// ビットマップインフォ構造体設定  
		lpBitInfo = (LPBITMAPINFO)GlobalAlloc(GPTR,sizeof(BITMAPINFO));
		lpBitInfo-> bmiHeader.biSize=sizeof(BITMAPINFOHEADER); 
		lpBitInfo-> bmiHeader.biWidth=FWIDTH;    
		lpBitInfo-> bmiHeader.biHeight=FHEIGHT;            
		lpBitInfo-> bmiHeader.biPlanes=1;                     
		lpBitInfo-> bmiHeader.biBitCount=24; // 24 bit      
		lpBitInfo-> bmiHeader.biCompression=BI_RGB;	// 非圧縮
		
		hDC = GetDC(hWnd);
		hDib = CreateDIBSection(hDC,lpBitInfo,0,(VOID**)&lpDibData,
			NULL, 0); 
		ReleaseDC(hWnd,hDC);
		
		// マスクの色設定
		rgbMask.rgbBlue=0;
		rgbMask.rgbGreen=0;
		rgbMask.rgbRed=0;
		
		break; 
		
	case WM_PAINT: // 描画(再描画のみ)
		
		PAINTSTRUCT ps;
		hDC = BeginPaint(hWnd,&ps);
		
		BitBlt(hDC,ps.rcPaint.left,ps.rcPaint.top,
			ps.rcPaint.right-ps.rcPaint.left,
			ps.rcPaint.bottom-ps.rcPaint.top,
			hBufDC,ps.rcPaint.left,ps.rcPaint.top,SRCCOPY);
		
		ReleaseDC(hWnd,hDC);
		
		break;
		
	case WM_LBUTTONDOWN:  // 左ボタン押した
		
		GetClientRect(hWnd,&rt);
		
		// 半透明処理
		CreateSemiTransBmp(hBufDC,hBgBitmap,hDib,lpDibData,
			LOWORD(lp),HIWORD(lp),rgbMask);
		
		hMemDC = CreateCompatibleDC(hBufDC);
		
		// ビットマップ貼り付け
		SelectObject(hMemDC,hBgBitmap);
		BitBlt(hBufDC,0,0,rt.right - rt.left,rt.bottom - rt.top,
			hMemDC,0,0,SRCCOPY);
		
		// DIB 貼り付け
		SelectObject(hMemDC,hDib);
		BitBlt(hBufDC,LOWORD(lp),HIWORD(lp),FWIDTH,FHEIGHT,
			hMemDC,0,0,SRCCOPY);

		DeleteDC(hMemDC);
		
		// 文字描画
		SetBkMode(hBufDC,TRANSPARENT);
		SetTextColor(hBufDC,RGB(255,255,0)) ;
		rt.left = LOWORD(lp);
		rt.top = HIWORD(lp);
		rt.right = rt.left + FWIDTH;
		rt.bottom = rt.top + FHEIGHT;
		wsprintf(szStr,"半透明っす");
		DrawText(hBufDC,szStr,strlen(szStr),&rt,
			DT_CENTER|DT_SINGLELINE|DT_VCENTER);
		
		InvalidateRect(hWnd,NULL,FALSE);
		break; 
		
	case WM_CLOSE: // クローズ
						  
		// 裏画面開放
		DeleteDC(hBufDC);
		DeleteObject(hBufBit);		
		
		// ビットマップ開放
		DeleteObject(hBgBitmap);
		DeleteObject(hDib);
		GlobalFree(lpBitInfo);
		
		DestroyWindow(hWnd);
		break;
		
	case WM_DESTROY: 
		PostQuitMessage(0);
		break;
		
	default:
		return(DefWindowProc(hWnd, msg, wp, lp));
    }
    return (0L);
}


// hSrcBitmap の一部分を半透明処理して hDib にセットする関数
VOID CreateSemiTransBmp(HDC hDC,
						HBITMAP hSrcBitmap, // 元のビットマップ
						HBITMAP hDib,  // DIB のハンドル
						LPBYTE lpDibData, // DIB のデータ配列
						LONG x, LONG y, // 座標
						RGBQUAD rgbMask // マスク
						){
	
	HDC hSrcDC,hDestDC;
	LONG dx,dy;
	WORD r,g,b;
	
	// 画像コピー
	hSrcDC = CreateCompatibleDC(hDC);
	hDestDC = CreateCompatibleDC(hDC);
	SelectObject(hSrcDC,hSrcBitmap);
	SelectObject(hDestDC,hDib);
	BitBlt(hDestDC,0,0,FWIDTH,FHEIGHT,hSrcDC,x,y,SRCCOPY);
	DeleteDC(hSrcDC);
	DeleteDC(hDestDC);
	
	// 半透明処理
	for(dy = 0;dy< FHEIGHT;dy++)
		for(dx = 0;dx< FWIDTH;dx++)
		{
			b = (WORD)lpDibData[(dy*FWIDTH+dx)*3];
			g = (WORD)lpDibData[(dy*FWIDTH+dx)*3+1];
			r = (WORD)lpDibData[(dy*FWIDTH+dx)*3+2];
			lpDibData[(dy*FWIDTH+dx)*3] = 
				(BYTE)((b+(WORD)rgbMask.rgbBlue)>> 1);
			lpDibData[(dy*FWIDTH+dx)*3+1] = 
				(BYTE)((g+(WORD)rgbMask.rgbGreen)>> 1);
			lpDibData[(dy*FWIDTH+dx)*3+2] = 
				(BYTE)((r+(WORD)rgbMask.rgbRed)>> 1);
		}
}