Eternal Windows
ビットマップ / 256色ビットマップ

これまで、使用してきたビットマップは全て24ビットを想定していましたが、 ビットマップには256色で表現されているものも数多く存在します。 DDBでビットマップを表しているような場合、 そのビットマップが24ビットであるか256色であるかといった詳細は、 全てデバイスが吸収してくれますから、単純にBitBltを呼び出すだけで、 どのようなビットマップでも正確にコピーすることができます。 しかし、DIBの基本はビットイメージからビットイメージへのコピーですから、 256色のビットマップを扱うならば、その取得方法を正しく理解しなければなりません。

実は256色ビットマップといっても、それは24ビットビットマップのように ビットイメージを持っており、GetObjectで取得することができます。 しかし、このビットイメージに実際の色は格納されていません。 そもそも24ビットというのは、ビットイメージに1つのピクセルの色を 表現する3バイト(24ビット)が格納されているから24ビットなのであり、 256色ビットイメージは1つのピクセルを1バイト(8ビット)で表現するため、 当然のことながら、この1バイトに色を格納することはできないわけです。 しかし、この1バイトはカラーテーブルのインデックスであるため、 カラーテーブルを参照することにより、実際の色を指すことができるようになります。 GetDIBColorTableという関数は、DIBセクションを選択しているデバイスコンテキストから カラーテーブルを取得します。

UINT GetDIBColorTable(
  HDC hdc,
  UINT uStartIndex,
  UINT cEntries,
  RGBQUAD *pColors
);

hdcは、DIBセクションを選択しているデバイスコンテキストのハンドルです。 uStartIndexは、カラーテーブルから最初に受け取るエントリを表すインデックスです。 cEntriesは、取得したいカラーテーブルのエントリ数です。 pColorsは、カラーテーブルの個々のエントリを表すRGBQUAD構造体のアドレスです。

カラーテーブルは、いわばそのビットマップが必要とする色の配列です。 既に述べてきたように、256色のビットイメージには実際の色は格納できないため、 その代わりとして自分が使いたい色で構成されている配列を用意し、 個々のエントリが256色ビットマップなりの24ビットを表現するわけです。 このエントリが正にRGBQUAD構造体であり、このRGBQUAD構造体が配列となったものが、 カラーテーブルと呼ばれているのです。 RGBQUAD構造体は、次のように定義されています。

typedef struct tagRGBQUAD {
  BYTE    rgbBlue; 
  BYTE    rgbGreen; 
  BYTE    rgbRed; 
  BYTE    rgbReserved; 
} RGBQUAD; 

見て分かるように、この構造体は色を表現するためのメンバを持っており、 メンバからも想像が付くように、それぞれの成分が格納されるにことになります。 rgbReservedは、予約されており0を格納します。 次に、カラーテーブルを取得して色をコピーする例を示します。

BYTE    index;
RGBQUAD rgb[256];

GetDIBColorTable(hdcSrc, 0, 256, rgb);

lpSrc = GetBits256(hbmpSrc, 0, 0);
index = *lpSrc;

lpDest[0] = rgb[index].rgbBlue;
lpDest[1] = rgb[index].rgbGreen;
lpDest[2] = rgb[index].rgbRed;

まず、GetDIBColorTableでカラーテーブルを取得します。 最初のエントリから取得したいので第2引数は0となり、 第3引数は全てのエントリを取得すべく256としています。 この256は、カラーテーブルが持ちえる最大のエントリ数でもありますが、 何故その値が256になるのでしょうか。 それは、カラーテーブルのインデクッスが8ビットとして 256色ビットマップのビットイメージに格納されているため、 もともと0から255までのエントリしか表すことができないからです。 次に、GetBits256という256色ビットマップを扱う自作関数でビットイメージを取得し、 それをインデックス用の変数に代入しています。 上記の例であれば、(0, 0)のピクセルのインデックスを取得したことになります。 後は、これをカラーテーブルの添え字にすれば、(0, 0)のピクセルの色を求めることができます。 GetBits256は今回のプログラムでも呼ばれており、その内部は次のようになっています。

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

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

基本的な構造は、これまでの節で呼び出してきたGetBitsと同じですが、 256色ビットマップは1バイト単位でインデックスという意味を持っているため、 ビットマップの幅やポインタを進めるときに3を掛ける必要はありません。 このことからも分かるように、256色ビットマップはカラーテーブルを考慮しても、 比較的24ビットビットマップよりサイズが軽いという特徴があります。

今回のプログラムは、ロードするビットマップを256色と仮定し、 バックバッファに描画します。

#include <windows.h>

void CopyBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, LPRGBQUAD lpRgb, COLORREF crTransparent);
LPBYTE GetBits(HBITMAP hbmp, int x, int y);
LPBYTE GetBits256(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 HDC     hdcMem = NULL;
	static HBITMAP hbmpMem = NULL;
	static HBITMAP hbmpMemPrev = NULL;
	static HDC     hdcBackbuffer = NULL;
	static HBITMAP hbmpBackbuffer = NULL;
	static HBITMAP hbmpBackbufferPrev = NULL;
	static RGBQUAD rgb[256] = {0};

	switch (uMsg) {

	case WM_CREATE: {
		HDC hdc;

		hdc = GetDC(hwnd);

		hdcMem = CreateCompatibleDC(hdc);
		hbmpMem = (HBITMAP)LoadImage(NULL, TEXT("sample256.bmp"), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);
		if (hbmpMem == NULL) {
			ReleaseDC(hwnd, hdc);
			return -1;
		}
		
		hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);
		GetDIBColorTable(hdcMem, 0, 256, rgb);

		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;
		
		GetObject(hbmpMem, sizeof(BITMAP), &bm);

		hdc = BeginPaint(hwnd, &ps);

		CopyBits(hbmpBackbuffer, 0, 0, bm.bmWidth, bm.bmHeight, hbmpMem, rgb, RGB(0, 255, 0));

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

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (hdcMem != NULL) {
			if (hbmpMem != NULL) {
				SelectObject(hdcMem, hbmpMemPrev);
				DeleteObject(hbmpMem);
			}
			DeleteDC(hdcMem);
		}

		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 CopyBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, LPRGBQUAD lpRgb, COLORREF crTransparent)
{
	int    x, y;
	BYTE   index;
	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  = GetBits256(hbmpSrc, 0, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			index = *lpSrc;
			if (lpRgb[index].rgbBlue != b || lpRgb[index].rgbGreen != g || lpRgb[index].rgbRed != r) {
				lpDest[0] = lpRgb[index].rgbBlue;
				lpDest[1] = lpRgb[index].rgbGreen;
				lpDest[2] = lpRgb[index].rgbRed;
			}
			
			lpSrc++;
			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;
}

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

	lp = (LPBYTE)bm.bmBits;
	lp += (bm.bmHeight - y - 1) * ((bm.bmWidth + 3) / 4) * 4;
	lp += 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);
}

まず、カラーテーブルを取得するコードを確認します。 次のコードは、WM_CREATEの一部です。

hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);
GetDIBColorTable(hdcChar, 0, 256, rgb);

GetDIBColorTableの呼び出し方は既に示した通りですが、 その前にデバイスコンテキストにDIBセクションを選択するのを忘れてはいけません。 また、ここで取得したカラーテーブルは実際に使うときのために 静的に宣言されているのも重要です。 ちなみに、GetDIBColorTableはカラーテーブルを持たない24ビットビットマップには 意味がありませんから、事前にビットマップのビット数を確認しておきたい場合もあります。 そのようなときは、BITMAP構造体のbmBitsPixelメンバが役に立ちます。

BITMAP bm;

GetObject(hbmpMem, sizeof(BITMAP), &bm);
if (bm.bmBitsPixel == 24)
	// 24ビットビットマップ
else if (bm.bmBitsPixel == 8)
	// 256色ビットマップ

次に、ビットマップをバックバッファに描画する関数を見ていきます。 この関数はいつものようにCopyBitsとなっていますが、 今回は256ビットマップが対象となるため、カラーテーブルを要求する引数が増えています。

void CopyBits(HBITMAP hbmpDest, int xStart, int yStart, int cx, int cy, HBITMAP hbmpSrc, LPRGBQUAD lpRgb, COLORREF crTransparent)
{
	int    x, y;
	BYTE   index;
	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  = GetBits256(hbmpSrc, 0, y);
		lpDest = GetBits(hbmpDest, xStart, yStart + y);
		for (x = 0; x < cx; x++) {
			index = *lpSrc;
			if (lpRgb[index].rgbBlue != b || lpRgb[index].rgbGreen != g || lpRgb[index].rgbRed != r) {
				lpDest[0] = lpRgb[index].rgbBlue;
				lpDest[1] = lpRgb[index].rgbGreen;
				lpDest[2] = lpRgb[index].rgbRed;
			}
			
			lpSrc++;
			lpDest += 3;
		}
	}
}

カラーテーブルを参照する以外のことは、基本的に24ビットのときと同じですが、 バックバッファが24ビットであるため、24ビットの処理と8ビットの処理が 混同することになり、うっかり片方の処理をもう片方に合わせてしまうことがあります。 たとえば、lpDestはバックバッファのビットイメージを指しますから、 個々のピクセルは3バイト単位で進めていかなければならず、 lpDest++とするわけにはいきません。


戻る