EternalWindows
ビットマップ / 反転と半透明

DIBの応用範囲は大変広いものがあります。 現在では、数多くのグラフィックスアルゴリズムが存在しますが、 ビットイメージにアクセスすることにより、それらの大半を実装できるでしょう。 今回は、実装するのがそれほど難しくないであろう反転処理と、 半透明処理に挑戦してみることにします。

反転というのは、ビットマップの向きを変えるということなので、 ピクセルをコピーする向きを変えればよいでしょう。

lpSrc  = GetBits(hbmpSrc, cx - 1, y);

このようにすれば、lpSrcは行の最後の列を指すことになります。 後は、lp -= 3 としていけば、逆向きにポインタが進むことになり、 コピー元の位置が本来の逆になります。

次に半透明ですが、これは単純に考えればコピー元とコピー先の色を 混ぜ合わすということです。 このとき、どれくらいの精度で混ぜ合わすかを示すのがアルファ値であり、 以下のような式を用いられることが多いと思われます。

dest = src * (alpha / 255) + dest * (1 - (alpha / 255));

alphaが255のときは、lpSrcの値がそのままlpDestにコピーされるというところが重要です。 このときは不透明であり、alphaが下がっていくにつれてlpSrcの色は透明になっていきます。 alphaを除算すると、その結果が小数点になることがあるため、 実際には、alphaをfloat型でキャストする必要があります。

今回のプログラムは、反転と半透明処理を実装しています。

#include <windows.h>

void MirrorBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent);
void AlphaBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent, BYTE alpha);
void CopyBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc);
LPBYTE GetBits(HBITMAP hbmp, int x, int y);
HBITMAP CreateBackbuffer(int nWidth, int nHeight);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;
	RECT       rc;
	DWORD      dwStyle;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;
	
	dwStyle = WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX;
	SetRect(&rc, 0, 0, 640, 480);
	AdjustWindowRect(&rc, dwStyle, FALSE);

	hwnd = CreateWindowEx(0, szAppName, szAppName, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HBITMAP hbmpMem = NULL;
	static HDC     hdcBackbuffer = NULL;
	static HBITMAP hbmpBackbuffer = NULL;
	static HBITMAP hbmpBackbufferPrev = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HDC hdc;

		hdc = GetDC(hwnd);
		
		hbmpMem = (HBITMAP)LoadImage(NULL, TEXT("sample.bmp"), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);
		if (hbmpMem == NULL) {
			ReleaseDC(hwnd, hdc);
			return -1;
		}
		
		hdcBackbuffer = CreateCompatibleDC(hdc);
		hbmpBackbuffer = CreateBackbuffer(640, 480);
		if (hbmpBackbuffer == NULL) {
			ReleaseDC(hwnd, hdc);
			return -1;
		}

		hbmpBackbufferPrev = (HBITMAP)SelectObject(hdcBackbuffer, hbmpBackbuffer);

		ReleaseDC(hwnd, hdc);

		return 0;
	}

	case WM_PAINT: {
		HDC         hdc;
		BITMAP      bm;
		PAINTSTRUCT ps;
		COLORREF    crTransparent = RGB(0, 255, 0);
		
		GetObject(hbmpMem, sizeof(BITMAP), &bm);

		hdc = BeginPaint(hwnd, &ps);

		MirrorBits(hbmpBackbuffer, 0, 0, bm.bmWidth, bm.bmHeight, hbmpMem, crTransparent);

		AlphaBits(hbmpBackbuffer, 0, bm.bmHeight + 10, bm.bmWidth, bm.bmHeight, hbmpMem, crTransparent, 60);

		BitBlt(hdc, 0, 0, 640, 480, hdcBackbuffer, 0, 0, SRCCOPY);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (hbmpMem != NULL)
			DeleteObject(hbmpMem);

		if (hdcBackbuffer != NULL) {
			if (hbmpBackbuffer != NULL) {
				SelectObject(hdcBackbuffer, hbmpBackbufferPrev);
				DeleteObject(hbmpBackbuffer);
			}
			DeleteDC(hdcBackbuffer);
		}
		
		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void MirrorBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent)
{
	int    x, y;
	BYTE   r, g, b;
	LPBYTE lpSrc, lpDest;
	
	b = (BYTE)(GetBValue(crTransparent));
	g = (BYTE)(GetGValue(crTransparent));
	r = (BYTE)(GetRValue(crTransparent));

	for (y = 0; y < cy; y++) {
		lpSrc  = GetBits(hbmpSrc, cx - 1, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			if (lpSrc[0] != b || lpSrc[1] != g || lpSrc[2] != r) {
				lpDest[0] = lpSrc[0];
				lpDest[1] = lpSrc[1];
				lpDest[2] = lpSrc[2];
			}
			
			lpSrc  -= 3;
			lpDest += 3;
		}
	}
}

void AlphaBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent, BYTE alpha)
{
	int    i;
	int    x, y;
	BYTE   aSrc[256], aDest[256];
	BYTE   r, g, b;
	LPBYTE lpSrc, lpDest;
	
	b = (BYTE)(GetBValue(crTransparent));
	g = (BYTE)(GetGValue(crTransparent));
	r = (BYTE)(GetRValue(crTransparent));
	
	for (i = 0; i < 256; i++) {
		aSrc[i]  = (BYTE)(i * alpha / 255);
		aDest[i] = (BYTE)(i - aSrc[i]);
	}

	for (y = 0; y < cy; y++) {
		lpSrc  = GetBits(hbmpSrc, 0, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			if (lpSrc[0] != b || lpSrc[1] != g || lpSrc[2] != r) {
				lpDest[0] = aSrc[lpSrc[0]] + aDest[lpDest[0]];
				lpDest[1] = aSrc[lpSrc[1]] + aDest[lpDest[1]];
				lpDest[2] = aSrc[lpSrc[2]] + aDest[lpDest[2]];
			}
			
			lpSrc  += 3;
			lpDest += 3;
		}
	}
}

LPBYTE GetBits(HBITMAP hbmp, int x, int y)
{
	BITMAP bm;
	LPBYTE lp;
	
	GetObject(hbmp, sizeof(BITMAP), &bm);

	lp = (LPBYTE)bm.bmBits;
	lp += (bm.bmHeight - y - 1) * ((3 * bm.bmWidth + 3) / 4) * 4;
	lp += 3 * x;

	return lp;
}

HBITMAP CreateBackbuffer(int nWidth, int nHeight)
{
	LPVOID           lp;
	BITMAPINFO       bmi;
	BITMAPINFOHEADER bmiHeader;

	ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
	bmiHeader.biSize      = sizeof(BITMAPINFOHEADER);
	bmiHeader.biWidth     = nWidth;
	bmiHeader.biHeight    = nHeight;
	bmiHeader.biPlanes    = 1;
	bmiHeader.biBitCount  = 24;

	bmi.bmiHeader = bmiHeader;

	return CreateDIBSection(NULL, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS, &lp, NULL, 0);
}

ビットマップを反転して描画するMiirorBitsは、次のようになっています。

void MirrorBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent)
{
	int    x, y;
	BYTE   r, g, b;
	LPBYTE lpSrc, lpDest;
	
	b = (BYTE)(GetBValue(crTransparent));
	g = (BYTE)(GetGValue(crTransparent));
	r = (BYTE)(GetRValue(crTransparent));

	for (y = 0; y < cy; y++) {
		lpSrc  = GetBits(hbmpSrc, cx - 1, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			if (lpSrc[0] != b || lpSrc[1] != g || lpSrc[2] != r) {
				lpDest[0] = lpSrc[0];
				lpDest[1] = lpSrc[1];
				lpDest[2] = lpSrc[2];
			}
			
			lpSrc  -= 3;
			lpDest += 3;
		}
	}
}

lpSrcのGetBitsで、cx - 1としているところが重要です。 これによりlpSrcは、行の最後の列を指すことになります。 -1するのは、cxが96だったとして、実際の列の範囲が0から95までだからです。 lpSrc -= 3; とすることにより、列の先頭へ進んでいきます。

ビットマップを半透明で描画するのは、AlphaBitsです。 実際のコードを確認する前に、まずはよくない例から見ていきましょう。

void AlphaBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent, BYTE alpha)
{
	int    x, y;
	BYTE   r, g, b;
	LPBYTE lpSrc, lpDest;

	b = (BYTE)(GetBValue(crTransparent));
	g = (BYTE)(GetGValue(crTransparent));
	r = (BYTE)(GetRValue(crTransparent));

	for (y = 0; y < cy; y++) {
		lpSrc  = GetBits(hbmpSrc, 0, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			if (lpSrc[0] != r || lpSrc[1] != g || lpSrc[2] != b) {
				lpDest[0] = lpSrc[0] * ((float)alpha / 255) + lpDest[0] * (1 - ((float)alpha / 255));
				lpDest[1] = lpSrc[1] * ((float)alpha / 255) + lpDest[1] * (1 - ((float)alpha / 255));
				lpDest[2] = lpSrc[2] * ((float)alpha / 255) + lpDest[2] * (1 - ((float)alpha / 255));
			}
			
			lpSrc  += 3;
			lpDest += 3;
		}
	}
}

このAlphaBitsは、プログラムで実装しているものと異なりますが、正しく動作します。 しかし、非常に効率が悪く低速です。 明らかに無駄といえるのは、alpha / 255 を何度も実行しているということです。 alphaは関数内では変更されないので、ループ文に入る前に alpha / 255 と、 1 - (alpha / 255) を計算しておくべきです。

void AlphaBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent, BYTE alpha)
{
	int    x, y;
	BYTE   r, g, b;
	float  fSrc, fDest;
	LPBYTE lpSrc, lpDest;

	b = (BYTE)(GetBValue(crTransparent));
	g = (BYTE)(GetGValue(crTransparent));
	r = (BYTE)(GetRValue(crTransparent));
	
	fSrc  = (float)alpha / 255;
	fDest = 1 - ((float)alpha / 255);

	for (y = 0; y < cy; y++) {
		lpSrc  = GetBits(hbmpSrc, 0, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			if (lpSrc[0] != r || lpSrc[1] != g || lpSrc[2] != b) {
				lpDest[0] = lpSrc[0] * fSrc + lpDest[0] * fDest;
				lpDest[1] = lpSrc[1] * fSrc + lpDest[1] * fDest;
				lpDest[2] = lpSrc[2] * fSrc + lpDest[2] * fDest;
			}
			
			lpSrc  += 3;
			lpDest += 3;
		}
	}
}

このようにすれば、同じ計算を何度も行わずに済みます。 同じ計算を回避するという考えは、ループ内のコードにも適応できます。 たとえば、lpSrc[0]が70で、fSrcが0.5のとき、その計算結果は35になります。 これは、言い換えれば、70を35に変換したといえるでしょう。 予め、変換用の配列を作成しておけば、その配列の添え字に色を指定するだけで、 fSrcと乗算された結果を導くことができます。

void AlphaBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, COLORREF crTransparent, BYTE alpha)
{
	int    i;
	int    x, y;
	BYTE   aSrc[256], aDest[256];
	BYTE   r, g, b;
	LPBYTE lpSrc, lpDest;
	
	b = (BYTE)(GetBValue(crTransparent));
	g = (BYTE)(GetGValue(crTransparent));
	r = (BYTE)(GetRValue(crTransparent));
	
	for (i = 0; i < 256; i++) {
		aSrc[i]  = (BYTE)(i * alpha / 255);
		aDest[i] = (BYTE)(i - aSrc[i]);
	}

	for (y = 0; y < cy; y++) {
		lpSrc  = GetBits(hbmpSrc, 0, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			if (lpSrc[0] != b || lpSrc[1] != g || lpSrc[2] != r) {
				lpDest[0] = aSrc[lpSrc[0]] + aDest[lpDest[0]];
				lpDest[1] = aSrc[lpSrc[1]] + aDest[lpDest[1]];
				lpDest[2] = aSrc[lpSrc[2]] + aDest[lpDest[2]];
			}
			
			lpSrc  += 3;
			lpDest += 3;
		}
	}
}

aSrcとaDestは変換テーブルで、各要素は変換後の色となります。 ループ文で計算を行っていては、ビットマップのサイズに比例して計算回数が増えますが、 変換テーブルの計算回数は常に固定です。


戻る