トップ 戻るゲームプログラミング練習帳
ビットマップ作成からマップスクロール、イベント処理まで

CreateDIBSectionによる汎用ビットマップ

これまでに、Windowsのビットマップとしてメモリ上にピクセルデータを保持して自由に処理できるDIBとGDIの描画機能で簡単に図形や文字を描けるDDBを見てきました。ただ、これらのビットマップはどちらももう一方の機能は利用できず、DIBにGDIで文字を描くことはできないし、DDBの画像データを直接メモリ操作でアクセスすることもできない(あるいはすべきでない)んですよね。
そこで、今回はこのDIB/DDB両方の特徴を持つ汎用的なビットマップ、GDIの機能を利用して描画できるHDC(に選択可能なHBITMAP)を持つDIBを、CreateDIBSection()で作ってみることにします。

DIBSection

CreateDIBSection()は、その名の通りDIBSectionと呼ばれる特殊なDIBを作成します。このDIBSectionは、DIB同様メモリ上にピクセル列(フレームバッファ)が取られプログラムからそのメモリ領域に自由にアクセスできるのですが、同時に返り値としてHBITMAPも取得できます。このHBITMAPを、メモリデバイスコンテキストに選択すれば、DDB同様そのデバイスコンテキストに対してGDIで描画できるのです。そしてその描画結果は、メモリ上のDIBのピクセル列にも反映されます。

DIBSectionを作成するには、まず作成するDIBSectionの形式をDIB同様BITMAPINFO構造体で定義します。そして、そのBITMAPINFOをCreateDIBSection()に渡すと指定形式のピクセル列がメモリ上に確保され、そのポインタを取得できます。ここで、ピクセル列は自分で確保するのではなく、システムが確保する点に注意してください。

今回は、256*256ピクセル、32ビットのDIBSectionを作ってみましょう。まず、BITMAPINFO構造体(内のBITMAPINFOHEADER)を設定します。BITMAPINFOHEADERのbiHeightは、GDIのデフォルト座標系にあわせるために、負の値にしてあります。

BITMAPINFO設定
ZeroMemory(&biBMP, sizeof(biBMP));

/* 256*256ピクセル、32ビットDIB用BITMAPINFO設定 */
biBMP.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
biBMP.bmiHeader.biBitCount = 32;
biBMP.bmiHeader.biPlanes = 1;
biBMP.bmiHeader.biWidth = 256;
biBMP.bmiHeader.biHeight = -256;

BITMAPINFOを定義したら、そのアドレスをCreateDIBSection()に渡し、ピクセル列のアドレス(引数で受け取り先を指定)とHBITMAP(返り値)を取得します。今回は、カラーテーブルは使わないので、引数のHDCはNULLを指定しています。

/* DIBSection作成 */
hbmpBMP = CreateDIBSection(NULL, &biBMP, DIB_RGB_COLORS, (void **)(&lpdwPixel), NULL, 0);

これで、lpdwPixelに作成されたDIBのアドレスが格納されたので、DIBとしてピクセルを参照できるようになりました。次に、hbmpBMPを選択するメモリデバイスコンテキストを作成し、GDIでもピクセルにアクセスできるようにしましょう。このメモリデバイスコンテキストの作成は、DDBとほぼ同じです。

メモリデバイスコンテキストの作成
/* ウインドウのHDC取得 */
hdc = GetDC(hwnd);

/* DIBSection用メモリデバイスコンテキスト作成 */
hdcBMP = CreateCompatibleDC(hdc);

/* ウインドウのHDC解放 */
ReleaseDC(hwnd, hdc);

/* DIBSectionのHBITMAPをメモリデバイスコンテキストに選択 */
hbmpOld = (HBITMAP)SelectObject(hdcBMP, hbmpBMP);

以上で、lpdwPixelを通した直接アクセス、hdcBMPを通したGDI描画と2種類の方法でDIBSectionに描画できるようになりました。さっそく、この2種類の方法でピクセルに描き込んでみましょう。今回は、赤、緑、青、白の256段階グラデーションをそれぞれの方法で描いてみました。
上半分は、メモリに直接書き込み、下半分のグラデーションはGDIのSetPixelで描画します。

DIBSectionへの描画
/* DIBSectionにグラデーション描画 */
for (i = 0;i < 256;i++) {
	for (j = 0;j < 32;j++) {

		/* DIBピクセル列に直接アクセス */
		lpdwPixel[i + j * 256] = (i << 16);
		lpdwPixel[i + (j + 32) * 256] = (i << 8);
		lpdwPixel[i + (j + 64) * 256] = i;
		lpdwPixel[i + (j + 96) * 256] = (i << 16) | (i << 8) | i;

		/* GDI経由で描画 */
		SetPixel(hdcBMP, i, j + 128, RGB(i, 0, 0));
		SetPixel(hdcBMP, i, j + 128 + 32, RGB(0, i, 0));
		SetPixel(hdcBMP, i, j + 128 + 64, RGB(0, 0, i));
		SetPixel(hdcBMP, i, j + 128 + 96, RGB(i, i, i));

	}
}

これで、DIBSectionの作成とピクセル処理は終了。後は、表示するだけですね。
表示は、DIBとしてもHDC(に選択中のHBITMAP)としても表示できます。DIBとして表示するならStretchDIBits()などで、HDCならBitBlt()などで表示することになるわけですが、今回は引数が少ないBitBlt()でウインドウのデバイスコンテキストに表示します。

/* DIBSectionを表示 */
BitBlt(hdc, 0, 0, 256, 256, hdcBMP, 0, 0, SRCCOPY);

さて今回の注目点は、GDIで読み書きした場合の精度です。たとえば、画面モードをフルカラー以外の16ビットや8ビットにした場合、RGB各8ビット(24/32ビット)のフルカラーではじめて完全に再現できる256段階のグラデーションは、メモリ上のピクセル列にどのように反映され、またGDIでピクセルの色を取得した場合、どうなるのか。
DDBであれば、8ビットや16ビットの画面モードで256段階のグラデーションを描き込んでGetPixel()でその色情報を読み出すと、画面(と互換性のあるメモリデバイスコンテキスト)にあわせて減色されるようです(少なくとも私が試した環境では)。つまり、フルカラー以外の色深度のメモリデバイスコンテキストに256段階のグラデーションを描いてその色を取得しようとしても、できません。デバイスコンテキストの色深度に合わせ、32段階とか数段階のグラデーションとして処理されてしまいます。
もし、DIBSectionでもそのような減色が起こるようだと、画面モードによってGDIの描画結果がまったく異なることになってしまうので、DIBSectionの魅力も損なわれてしまいそうですね。

そこで、そのあたりがどうなっているのか調べるために、ビットマップが表示されている部分にマウスを持ってくるとその下のピクセルの色をピクセル列への直接アクセスとGDI(GetPixel())で取得し、ウインドウのタイトルバーに表示するようにしてみました。

ピクセルRGBの取得
case WM_MOUSEMOVE:

	/* マウスカーソル座標取得 */
	iX = LOWORD(lParam);
	iY = HIWORD(lParam);

	if (iX > 255 || iY > 255) {
		return 0;
	}

	/* カーソル位置のピクセルRGBをピクセル列とGDIから取得し、文字列化 */
	wsprintf(aszTmp, "(%d, %d) - Buf:%08x GDI:%08x",
	         iX, iY, lpdwPixel[iX + iY * 256], GetPixel(hdcBMP, iX, iY));

	/* ピクセルのRGBをタイトルバーに表示 */
	SetWindowText(hwnd, aszTmp);

	return 0;

表示される数値は16進表記で、最上位2桁が0、下位24ビットが色情報です。ピクセル列の値(Buf)は、上位からRGB各2桁、GDIの方はBGR各2桁とRとBが入れ替わっているので注意してください。

私が試した環境では、8/16ビットで実行してもGDIの読み書きはフルカラーの精度で行われる(減色はなし)ようですね。DIBSectionを通すことで、GDIの描画結果をデバイスコンテキストの色深度によらずフルカラーでメモリ上に取得できる、ということなのでしょうか。もしそうだとしたら、なかなか便利かも。

実験を終えたら、DIBSectionを削除します。

/* DIBSectionをメモリデバイスコンテキストの選択から外す */
SelectObject(hdcBMP, hbmpOld);

/* DIBSectionを削除 */
DeleteObject(hbmpBMP);

/* メモリデバイスコンテキストを削除 */
DeleteDC(hdcBMP);

プログラム

実行すると、256×256ピクセルの32ビットDIBSectionを作成し、グラデーションを描画してからウインドウ上に描画します。
マウスカーソルをグラデーションの上に持っていくと、そこの色情報を取得できますので、画面モードを変えて試してみましょう。

プログラムソース表示 プログラムダウンロード


トップ 戻るシーザム (防犯・護身用品専門店)