・MMXで半透明(1) : C 編 (99/5/11)

以前半透明処理の話をしましたが、 その時はただ色の平均値を取る方式だったのでそれほど難しい話では ありませんでした。今回は平均ではなくて、画像 1 をα %、画像 2 を (1-α) %だけ混ぜ合わせるような半透明処理、いわゆる「α合成 (Alpha Blending)」について説明し、ついでなので MMX 機能を使って α合成を行ってみます。

α合成の例

||

とりあえず今回は C でやってみましょう。 α合成の基本的な考え方は、元の絵とマスクしたい絵に a と(1-a) を掛けて 足すということです。つまり、元の絵のある座標(x,y) の点の色が RGB(r1,g1,b1) で、マスクしたい色が RGB(r2,g2,b2) だとしたら、α合成を した後の点(x,y) の色は RGB(r1*a+r2*(1-a),g1*a+g2*(1-a),b1*a+b2*(1-a)) となります。とくに a = 0.5 の時は以前の半透明処理の話と同じになります。 それを C で書くと次のようになります。αには 0 から 256 までを指定します。 なお簡単のために 24 bit ビットマップを使用し、コピー先と コピー元 1 とコピー元 2 の画像サイズは同じにして考えます。 16 bit の時は RGB 値の分解を行う必要がありますが 基本的には同じです。256 色の時は素直に変換テーブルを用意した方が良いです。

// α合成(C 版)  
//
// コピー先 = コピー元1 * α + コピー元1 * (1-α)
//
VOID AlphaBlending(LPBYTE lpDest, // コピー先
				LPBYTE lpSrc1,    // コピー元1
				LPBYTE lpSrc2,    // コピー元2
				DWORD dwLength,   // コピーする長さ
				WORD wAlpha){     // α値

	DWORD i;
	WORD wAlpha2 = 256 - wAlpha;

	for(i = 0;i < dwLength ; i+=3){
			// 青
			lpDest[i] = 
				(BYTE)( ((WORD)lpSrc1[i]*wAlpha >> 8 ) + ((WORD)lpSrc2[i]*wAlpha2  >> 8) );
			// 緑
			lpDest[i+1] = 
				(BYTE)( ((WORD)lpSrc1[i+1]*wAlpha >> 8) + ((WORD)lpSrc2[i+1]*wAlpha2 >> 8) );
			// 赤
			lpDest[i+2] = 
				(BYTE)( ((WORD)lpSrc1[i+2]*wAlpha >> 8) + ((WORD)lpSrc2[i+2]*wAlpha2 >> 8) );

		}
}

まあ、簡単ですね。ただ、画像 1(lpSrc1) と 画像 2(lpSrc2) の RGB 値をそれぞれ wAlpha 倍、 wAlpha2(= 1-wAlpha) 倍して 256 で割って 足してコピー先画像(lpDest) に代入しているだけです。

C で素直に書けばこういうソースになりますが、問題は「超ょー遅い」 という事です。MMX ペンティアム 150 で 460x320 のビットマップ 2 枚を混合させる場合、一秒間に平均 8 回しか計算できません。 8 回ではシューティングゲームなどリアルタイムに合成しなければ ならない場合にはとても使えません。高橋名人の連打よりも遅いです。 よってどうしても C を使ってリアルタイム にα合成をさせたいならば変換値を入れたテーブルをあらかじめ用意しておく 事が必要になります。もちろんその分余分なメモリを食うことになりますが。

なお test.bmp、test2.bmp という 2 枚のビットマップを合成する プログラムの全体は次のようになります。ビットマップのサイズは 460x320 に合わせて下さい。 クライアント領域でマウスを左クリックするとαの値を変更し、 右クリックすると計算回数のテストを行います。一応 VC++6.0 用ですが、 肝心の合成を行う関数(VOID AlphaBlending() ) は他のコンパイラや OS でも 使えると思います。それとリンクに winmm.lib を追加しておいて下さい。

(第2回 アセンブラ編に続く)


// アルファ合成サンプル 99/5/10
// (C) tokai@fides.dti.ne.jp

#include < windows.h>

#define BITWIDTH 460 // ビットマップ幅
#define BITHEIGHT 320 // ビットマップ高さ
#define TESTTIME 10000 // テストする時間

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL Create24BitDIB(HWND,HBITMAP*,LPBITMAPINFO,LPBYTE*,DWORD,DWORD);
VOID PasteBitmapToDIB(HDC,HINSTANCE,HBITMAP,LPSTR);
VOID AlphaBlending(LPBYTE,LPBYTE,LPBYTE,DWORD,WORD);

char szClassName[] = "ALPHABLENDING";

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;
    }
	
	// ウィンドウ作成
	RECT rt;
	rt.left = 0;rt.right = BITWIDTH;
	rt.top  = 0;rt.bottom = BITHEIGHT;
	AdjustWindowRect(&rt,WS_OVERLAPPEDWINDOW,FALSE);
    hWnd = CreateWindow(szClassName,
        "α合成",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
		rt.right-rt.left,rt.bottom-rt.top,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;

	static WORD wAlpha;
	static DWORD dwSize;

	// 描画画面
	static HDC hBufDC; 
    static HBITMAP hBufDib;
	static LPBYTE lpBufDibByte;
	static LPBITMAPINFO lpBufDibBitInfo;
		
	// DIB 
	static HBITMAP hDib1,hDib2;
	static LPBYTE lpDibByte1,lpDibByte2;
	static LPBITMAPINFO lpDibBitInfo1,lpDibBitInfo2;

    switch (msg) {
		
	case WM_CREATE: // 初期化	
		
		// インスタンス取得
		hInst = (HINSTANCE)GetWindowLong(hWnd,GWL_HINSTANCE); 
		
		// 24bit DIB 作成
		Create24BitDIB(hWnd,&hBufDib,lpBufDibBitInfo,&lpBufDibByte,BITWIDTH,BITHEIGHT);
		Create24BitDIB(hWnd,&hDib1,lpDibBitInfo1,&lpDibByte1,BITWIDTH,BITHEIGHT);
		Create24BitDIB(hWnd,&hDib2,lpDibBitInfo2,&lpDibByte2,BITWIDTH,BITHEIGHT);

		// 描画画面作成
		hDC = GetDC(hWnd);
		hBufDC = CreateCompatibleDC(hDC);
		SelectObject(hBufDC,hBufDib);
		ReleaseDC(hWnd,hDC);

		// DIB1 にビットマップコピー(test.bmp)
		PasteBitmapToDIB(hBufDC,hInst,hDib1,"test.bmp");

		// DIB2 にビットマップコピー(test2.bmp)
		PasteBitmapToDIB(hBufDC,hInst,hDib2,"test2.bmp");

		// 半透明処理
		wAlpha = 128;
		dwSize = BITWIDTH*BITHEIGHT*3;
		AlphaBlending(lpBufDibByte,lpDibByte1,lpDibByte2,dwSize,wAlpha);

		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:  // 左ボタン押した
				
		// 半透明処理
		wAlpha = (wAlpha + 8) & 0xff;
		AlphaBlending(lpBufDibByte,lpDibByte1,lpDibByte2,dwSize,wAlpha);

		InvalidateRect(hWnd,NULL,FALSE);
		break; 

	case WM_RBUTTONDOWN:  // 右ボタン押した

		DWORD dwStartTime,dwTime;
		CHAR szStr[256];
		int i;

		if(MessageBox(hWnd,"テストを開始しますか?","確認",MB_YESNO)==IDYES){
			
			i = 0;
			dwTime = TESTTIME;
			dwStartTime = timeGetTime();
			
			while(timeGetTime()-dwStartTime < dwTime){
				AlphaBlending(lpBufDibByte,lpDibByte1,lpDibByte2,dwSize,wAlpha);
				i++;
			}
			
			wsprintf(szStr,"%d 回/%d 秒\n\n平均 %d 回",i,dwTime/1000,(int)((double)i/(double)(dwTime/1000)));
			MessageBox(hWnd,szStr,"結果",MB_OK);
		}

		break;

	case WM_CLOSE: // クローズ
						  
		// 描画画面開放
		DeleteDC(hBufDC);
		DeleteObject(hBufDib);
		GlobalFree(lpBufDibBitInfo);
		
		// DIB 開放
		DeleteObject(hDib1);
		GlobalFree(lpDibBitInfo1);
		DeleteObject(hDib2);
		GlobalFree(lpDibBitInfo2);
		
		DestroyWindow(hWnd);

		break;
		
	case WM_DESTROY: 
		PostQuitMessage(0);
		break;
		
	default:
		return(DefWindowProc(hWnd, msg, wp, lp));
    }
    return (0L);
}



// 24bit DIB 作成
BOOL Create24BitDIB(HWND hWnd,HBITMAP* lphDib,LPBITMAPINFO lpDibBitInfo,LPBYTE* lppDibByte,
					DWORD dwWidth,DWORD dwHeight){
	
	HDC hDC;

	// 24 bit DIB の場合は横幅が 4 の倍数でないと駄目
	if(dwWidth % 4 != 0) return FALSE;
	
	// ビットマップインフォ構造体設定  
	lpDibBitInfo = (LPBITMAPINFO)GlobalAlloc(GPTR,sizeof(BITMAPINFO));
	lpDibBitInfo-> bmiHeader.biSize=sizeof(BITMAPINFOHEADER); 
	lpDibBitInfo-> bmiHeader.biWidth=dwWidth;    
	lpDibBitInfo-> bmiHeader.biHeight=dwHeight;            
	lpDibBitInfo-> bmiHeader.biPlanes=1;                     
	lpDibBitInfo-> bmiHeader.biBitCount=24; // 24 bit      
	lpDibBitInfo-> bmiHeader.biCompression=BI_RGB;	// 非圧縮
	
	hDC = GetDC(hWnd);
	*lphDib = CreateDIBSection(hDC,lpDibBitInfo,0,(VOID**)lppDibByte,NULL, 0); 
	ReleaseDC(hWnd,hDC);

	return TRUE;
}


// DIB にビットマップをコピー
VOID PasteBitmapToDIB(HDC hDC,HINSTANCE hInst,HBITMAP hDib,LPSTR szBitmapFile)
{

	HBITMAP hBitmap;
	HDC hMemDC,hMemDC2;

	hBitmap = (HBITMAP)LoadImage(hInst,szBitmapFile,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

	hMemDC = CreateCompatibleDC(hDC);
	hMemDC2 = CreateCompatibleDC(hDC);
	SelectObject(hMemDC,hDib);
	SelectObject(hMemDC2,hBitmap);
	BitBlt(hMemDC,0,0,BITWIDTH,BITHEIGHT,hMemDC2,0,0,SRCCOPY);
	DeleteDC(hMemDC);
	DeleteDC(hMemDC2);

	DeleteObject(hBitmap);
}


// α合成(C 版)  
//
// コピー先 = コピー元1 * α + コピー元1 * (1-α)
//
VOID AlphaBlending(LPBYTE lpDest, // コピー先
				LPBYTE lpSrc1,    // コピー元1
				LPBYTE lpSrc2,    // コピー元2
				DWORD dwLength,   // コピーする長さ
				WORD wAlpha){     // α値

	DWORD i;
	WORD wAlpha2 = 256 - wAlpha;

	for(i = 0;i < dwLength ; i+=3){
			// 青
			lpDest[i] = 
				(BYTE)( ((WORD)lpSrc1[i]*wAlpha >> 8 ) + ((WORD)lpSrc2[i]*wAlpha2  >> 8) );
			// 緑
			lpDest[i+1] = 
				(BYTE)( ((WORD)lpSrc1[i+1]*wAlpha >> 8) + ((WORD)lpSrc2[i+1]*wAlpha2 >> 8) );
			// 赤
			lpDest[i+2] = 
				(BYTE)( ((WORD)lpSrc1[i+2]*wAlpha >> 8) + ((WORD)lpSrc2[i+2]*wAlpha2 >> 8) );

		}
}