DIB と DDB


DIB から DDB への変換

DIB の情報ヘッダとピクセルビットへのポインタさえ取得できれば
SetDIBitsToDevice() 関数などで、指定デバイスに画像をコピーできました

しかし、これらの過程には BitBlt() 関数とは異なり
DIB ピクセルデータをデバイス依存のデータに変換するという作業があります
8ビットディスプレイなどに 24 ビット DIB を表示するなら、さらに近似色の検索も必要です
これは、現在のCPUならそれほど苦になる作業ではありませんが
それでも DDB に比べれば、かなり計算量が増加するのです

1度だけ DIB を描画するならば、その速度はコンピュータの性能に依存します
同一の DIB を何度も描画するのならば、その速度はプログラマの知識に依存するでしょう

何度も描画や拡大/縮小処理を繰り返すような DIB は
DIB として行うよりも、DDB として、すなわち BitBlt() 関数などのほうが高速に動作します
(1度だけの描画なら、どちらにせよ DDB の変換が必要であり速度は同じ)

DIB を DDB に変換するには CreateDIBitmap() 関数を使います
この関数は、DIB を元に DDB を作成するのですが CreateDIB という奇妙な名前を持ちます
この関数が作成するのは DDB であるということに注意してください
HBITMAP CreateDIBitmap(
	HDC hdc ,
	CONST BITMAPINFOHEADER *lpbmih ,
	DWORD fdwInit ,
	CONST VOID *lpbInit ,
	CONST BITMAPINFO *lpbmi ,
	UINT fuUsage
);
hdc には、デバイスコンテキストのハンドルを指定します
このデバイスと互換性のある DDB を作成します

lpbmih は DIB 情報ヘッダへのポインタを指定します
関数がビットマップの希望の幅や高さなどの情報を取得するために指定します

fdwInit は作成したビットマップを初期化するかどうかを数値で表します
0 の場合は初期化しないことを表し、CBM_INIT を指定すれば
lpbInit、lpbmi、fuUsage 引数を用いてビットマップを初期化することを表します

lpbInit はピクセルビットへのポインタ、lpbmi は情報ヘッダへのポインタ
fuUsage は DIB のピクセルデータが RGB 値かパレットを使うかをあらわす定数を指定します
ここで指定するカラー情報フラグは SetDIBitsToDevice() 関数を参照してください
これら3つの引数は fdwInit に CBM_INIT を指定した時のみ使われます
関数が成功すればビットマップのハンドル、失敗すれば NULL が返ります

この関数は、必ずしも DIB を用いて初期化しなければならないというわけではなく
次のように、DIB 情報ヘッダを用いて DDB を作成することもできます

CreateDIBitmap(hdc , lpbmih , 0 , NULL , NULL , 0);

これは、CreateCompatibleBitmap() 関数と同様のアプローチです
唯一違うのは、BITMAPINFOHEADER 構造体を使って希望を伝えているいるところです

第三引数に CBM_INIT を指定して
情報ヘッダとピクセルビットへのポインタを渡せば
DIB のピクセル情報を DDB に変換したビットマップのハンドルが返ります
#include <windows.h>

BITMAPFILEHEADER bmpFileHeader;
BITMAPINFO * bmpInfo;
BYTE * bPixelBits;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	static BITMAP bitmap;
	static HBITMAP hBitmap;
	static HDC hBuffer;

	switch (msg) {
	case WM_DESTROY:
		free(bPixelBits);
		free(bmpInfo);
		DeleteDC(hBuffer);
		DeleteObject(hBitmap);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		hdc = GetDC(hWnd);

		hBitmap = CreateDIBitmap(
			hdc , &bmpInfo->bmiHeader ,
			CBM_INIT , bPixelBits , bmpInfo , DIB_RGB_COLORS
		);
		hBuffer = CreateCompatibleDC(hdc);
		SelectObject(hBuffer , hBitmap);

		GetObject(hBitmap , sizeof (BITMAP) , &bitmap);
		ReleaseDC(hWnd , hdc);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);

		BitBlt(	hdc , 0 , 0 , bitmap.bmWidth , bitmap.bmHeight ,
			hBuffer , 0 , 0 , SRCCOPY
		);

		EndPaint(hWnd , &ps);
		return 0;
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;
	HANDLE hFile;
	DWORD dwBytes;

	hFile = CreateFile(lpCmdLine , GENERIC_READ , 0 , NULL ,
		OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) return 1;

	ReadFile(hFile , &bmpFileHeader , sizeof (BITMAPFILEHEADER) , &dwBytes , NULL);
	if (bmpFileHeader.bfType != 0x4D42) {
		MessageBox(NULL , TEXT("This is not a bitmap file") , NULL , MB_OK);
		return 1;
	}

	bmpInfo = (BITMAPINFO *) malloc (bmpFileHeader.bfOffBits - dwBytes);
	ReadFile(hFile , bmpInfo , bmpFileHeader.bfOffBits - dwBytes , &dwBytes , NULL);
	bPixelBits = (BYTE *) malloc (bmpFileHeader.bfSize - bmpFileHeader.bfOffBits);
	ReadFile(hFile ,bPixelBits ,
		bmpFileHeader.bfSize - bmpFileHeader.bfOffBits , &dwBytes , NULL);
	CloseHandle(hFile);

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
これまでのように、コマンドライン引数でビットマップファイルを指定すると
そのビットマップファイルをクライアント領域に描画します

WM_PAINT の処理を見てください
ディスクから読み取った DIB を最終的に BitBlt() 関数で表示しています
これは、WM_CREATE で DIB を DDB に変換し
そのビットマップをメモリデバイス hBuffer にセットしているから可能になった処理です

CreateDIBitmap() 関数では初期化せずに、後から初期化することもできます
例えば、描画するかどうかその時点では判断できない場合は
情報ヘッダを元にビットマップのみを作っておき、初期化はせずに置いておきます
そして、ユーザーの何らかの操作で必要になった時に初期化するという方法をとれます

DIB のデータを用いてビットマップにピクセルを設定するには
SetDIBits() 関数を使います
int SetDIBits(
	HDC hdc,
	HBITMAP hbmp,
	UINT uStartScan,
	UINT cScanLines,
	CONST VOID *lpvBits,
	CONST BITMAPINFO *lpbmi,
	UINT fuColorUse
);
hdc にはデバイスコンテキストを指定します
hbmp は変更するビットマップのハンドルを指定します

uStartScan は lpvBits のデバイス独立の色データの最初の走査行を指定します
cScanLines は走査線の数です
これらの意味は SetDIBitsToDevice() 関数で説明しましたね

lpvBits は DIB の色データが入ったバイト配列へのポインタを
lpbmi には DIB の情報が入ったBITMAPINFO 構造体へのポインタを指定します
fuColorUse はカラー情報フラグを指定します(SetDIBitsToDevice() 参照)

関数が成功すれば、コピーされた走査線の数が、失敗すれば 0 が返ります
#include <windows.h>

BITMAPFILEHEADER bmpFileHeader;
BITMAPINFO * bmpInfo;
BYTE * bPixelBits;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	static BITMAP bitmap;
	static HBITMAP hBitmap;
	static HDC hBuffer;

	switch (msg) {
	case WM_DESTROY:
		free(bPixelBits);
		free(bmpInfo);
		if (hBuffer) DeleteDC(hBuffer);
		DeleteObject(hBitmap);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		hdc = GetDC(hWnd);

		hBitmap = CreateDIBitmap(
			hdc , &bmpInfo->bmiHeader , 0 , NULL , NULL , 0
		);
		GetObject(hBitmap , sizeof (BITMAP) , &bitmap);

		ReleaseDC(hWnd , hdc);
		return 0;
	case WM_LBUTTONDOWN:
		if (hBuffer) return 0;
		hdc = GetDC(hWnd);

		SetDIBits(
			hdc , hBitmap , 0 , bitmap.bmHeight ,
			bPixelBits , bmpInfo , DIB_RGB_COLORS
		);
		hBuffer = CreateCompatibleDC(hdc);
		SelectObject(hBuffer , hBitmap);

		ReleaseDC(hWnd , hdc);
		return 0;
	case WM_PAINT:
		if (!hBuffer) return 0;
		hdc = BeginPaint(hWnd , &ps);

		BitBlt(	hdc , 0 , 0 , bitmap.bmWidth , bitmap.bmHeight ,
			hBuffer , 0 , 0 , SRCCOPY
		);

		EndPaint(hWnd , &ps);
		return 0;
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;
	HANDLE hFile;
	DWORD dwBytes;

	hFile = CreateFile(lpCmdLine , GENERIC_READ , 0 , NULL ,
		OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) return 1;

	ReadFile(hFile , &bmpFileHeader , sizeof (BITMAPFILEHEADER) , &dwBytes , NULL);
	if (bmpFileHeader.bfType != 0x4D42) {
		MessageBox(NULL , TEXT("This is not a bitmap file") , NULL , MB_OK);
		return 1;
	}

	bmpInfo = (BITMAPINFO *) malloc (bmpFileHeader.bfOffBits - dwBytes);
	ReadFile(hFile , bmpInfo , bmpFileHeader.bfOffBits - dwBytes , &dwBytes , NULL);
	bPixelBits = (BYTE *) malloc (bmpFileHeader.bfSize - bmpFileHeader.bfOffBits);
	ReadFile(hFile ,bPixelBits ,
		bmpFileHeader.bfSize - bmpFileHeader.bfOffBits , &dwBytes , NULL);
	CloseHandle(hFile);

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
このプログラムは、先ほどのプログラムの一部を改良したものです
プログラムを起動した時点では、ビットマップを作成するだけで初期化を行いません
つまり、まだ DIB は DDB に変換されていないのです

起動後、ウィンドウのクライアント領域を左クリックすれば
SetDIBits() 関数によって作成したビットマップに DIB がセットされます


DDB から DIB への変換

SetDIBits() 関数などによって、DIB を DDB に変換することができました
すると、誰もが DDB を DIB に変換できないものかと考えるでしょう
確かに、Windows API は DDB を DIB に変換する関数をサポートしています

しかし、DDB は DIB に必要な全ての情報を持っていません
DDB に変換した DIB を、再び DIB に戻したとしても、それは同じではありません
画像の描画に必要な、完全なカラー情報は失われる可能性があります
どの程度の情報が失われるかは、ビデオモードによって異なるでしょう

例えばフルカラーの DIB を16色のデバイス用の DDB に変換したとします
カラー情報量の少ないデバイスの DDB を DIB に変換すれば
当然、最初のフルカラーの DIB のピクセル情報は失われることになります

GetDIBits() 関数は、DDB を指定した形式に変換します
int GetDIBits(
	HDC hdc , HBITMAP hbmp ,
	UINT uStartScan , UINT cScanLines ,
	LPVOID lpvBits , LPBITMAPINFO lpbi,
	UINT uUsage
);
hdc はデバイスコンテキストのハンドル、
hbmp は変換するビットマップのハンドルを指定します

uStartScan は取得する最初の走査線、cScanLines は走査線の数を指定します
lpvBits には変換したビットマップデータを受けるバッファを指定します
ここに NULL を指定すると、lpbi にビットマップのサイズと形式が格納されます
lpbi は DIB について希望する形式を指定する BITMAPINFO 構造体へのポインタを、
uUsage はカラー情報フラグを指定します(SetDIBitsToDevice() 参照)

GetDIBits() 関数は、そう頻繁に使われる関数ではありませんが
DDB を DIB に変換してディスクファイルに保存する方法の一つとして利用できます
#include <windows.h>

TCHAR strFileName[1024];
BITMAPFILEHEADER bmpFileHeader;
BITMAPINFO * bmpInfo;
BYTE * bPixelBits;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	HANDLE hFile;
	PAINTSTRUCT ps;
	DWORD dwBytes;
	static BITMAP bitmap;
	static HBITMAP hBitmap;
	static HDC hBuffer;

	switch (msg) {
	case WM_DESTROY:
		free(bPixelBits);
		free(bmpInfo);
		DeleteDC(hBuffer);
		DeleteObject(hBitmap);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		hdc = GetDC(hWnd);

		hBitmap = CreateDIBitmap(
			hdc , &bmpInfo->bmiHeader ,
			CBM_INIT , bPixelBits , bmpInfo , DIB_RGB_COLORS
		);
		hBuffer = CreateCompatibleDC(hdc);
		SelectObject(hBuffer , hBitmap);
		GetObject(hBitmap , sizeof (BITMAP) , &bitmap);

		BitBlt(	hBuffer , 0 , 0 , bitmap.bmWidth , bitmap.bmHeight ,
			hBuffer , 0 , 0 , NOTSRCCOPY
		);
		GetDIBits(
			hdc , hBitmap , 0 , bitmap.bmHeight ,
			bPixelBits , bmpInfo , DIB_RGB_COLORS
		);
		ReleaseDC(hWnd , hdc);

		hFile = CreateFile(strFileName , GENERIC_WRITE , 0 , NULL ,
			CREATE_NEW , FILE_ATTRIBUTE_NORMAL , NULL);
		if (hFile == INVALID_HANDLE_VALUE) return 0;

		WriteFile(	hFile , &bmpFileHeader ,
			sizeof (BITMAPFILEHEADER) , &dwBytes , NULL);
		WriteFile(	hFile , bmpInfo ,
			bmpFileHeader.bfOffBits - sizeof (BITMAPFILEHEADER) ,
			&dwBytes , NULL);
		WriteFile( hFile , bPixelBits ,
			bmpFileHeader.bfSize - bmpFileHeader.bfOffBits ,
			&dwBytes , NULL);
		CloseHandle(hFile);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);

		BitBlt(	hdc , 0 , 0 , bitmap.bmWidth , bitmap.bmHeight ,
			hBuffer , 0 , 0 , SRCCOPY
		);

		EndPaint(hWnd , &ps);
		return 0;
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;
	HANDLE hFile;
	DWORD dwBytes;

	hFile = CreateFile(lpCmdLine , GENERIC_READ , 0 , NULL ,
		OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) return 1;

	ReadFile(hFile , &bmpFileHeader , sizeof (BITMAPFILEHEADER) , &dwBytes , NULL);
	if (bmpFileHeader.bfType != 0x4D42) {
		MessageBox(NULL , TEXT("This is not a bitmap file") , NULL , MB_OK);
		return 1;
	}

	bmpInfo = (BITMAPINFO *) malloc (bmpFileHeader.bfOffBits - dwBytes);
	ReadFile(hFile , bmpInfo , bmpFileHeader.bfOffBits - dwBytes , &dwBytes , NULL);
	bPixelBits = (BYTE *) malloc (bmpFileHeader.bfSize - bmpFileHeader.bfOffBits);
	ReadFile(hFile ,bPixelBits ,
		bmpFileHeader.bfSize - bmpFileHeader.bfOffBits , &dwBytes , NULL);
	CloseHandle(hFile);

	*(lpCmdLine + lstrlen(lpCmdLine) - 4) = 0;
	wsprintf(strFileName , TEXT("%s_not.bmp") , lpCmdLine);

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
このプログラムは、コマンドラインで指定した Windows DIB を読み込み
色を反転して、同一フォルダに "ファイル名_not.bmp" という名前で保存します

ソースコードは長いですが、重要なのは WM_CREATE の処理です
まず、BitBlt() でメモリデバイスコンテキストの色を反転させます
そして、この色を反転した DDB を再び DIB に変換してファイルに保存しているのです



左が読み込んだファイル、右が出力したファイルです
色を反転させた DDB を DIB に変換し、正しく保存されていることが確認できます


CreateDIBitmap()

HBITMAP CreateDIBitmap(
	HDC hdc ,
	CONST BITMAPINFOHEADER *lpbmih ,
	DWORD fdwInit ,
	CONST VOID *lpbInit ,
	CONST BITMAPINFO *lpbmi ,
	UINT fuUsage
);
デバイス独立ビットマップ (DIB) からデバイス依存ビットマップ (DDB) を作成します

hdc - デバイスコンテキストのハンドルを指定します
lpbmih - DIB 情報ヘッダへのポインタを指定します
fdwInit - 初期化しないならば 0、するのならば CBM_INIT を指定します
lpbInit - ピクセルビットへのポインタを指定します
lpbmi - 情報ヘッダへのポインタを指定します
fuUsage - カラー情報フラグを指定します

戻り値 - 関数が成功すればビットマップのハンドル、失敗すれば NULL が返ります

fuUsage には次の定数のいずれかを指定します

定数解説
DIB_PAL_COLORS カラーテーブルに論理パレットに対する
16 ビットインデックスが格納されている
DIB_RGB_COLORS カラーテーブルに RGB 値が格納されている

SetDIBits()

int SetDIBits(
	HDC hdc,
	HBITMAP hbmp,
	UINT uStartScan,
	UINT cScanLines,
	CONST VOID *lpvBits,
	CONST BITMAPINFO *lpbmi,
	UINT fuColorUse
);
指定されたデバイス独立ビットマップ (DIB) の色データを使って
ビットマップにピクセルを設定します

hdc - デバイスコンテキストを指定します
hbmp - 変更するビットマップのハンドルを指定します
uStartScan - lpvBits のデバイス独立の色データの最初の走査行を指定します
cScanLines 走査線の数を指定します
lpvBits - DIB の色データが入ったバイト配列へのポインタを指定します
lpbmi - DIB の情報が入ったBITMAPINFO 構造体へのポインタを指定します
fuColorUse - カラー情報フラグを指定します

戻り値 - 成功すれば、コピーされた走査線の数、失敗すれば 0

fuColorUse には次の定数のいずれかを指定します

定数解説
DIB_PAL_COLORS カラーテーブルに論理パレットに対する
16 ビットインデックスが格納されている
DIB_RGB_COLORS カラーテーブルに RGB 値が格納されている

GetDIBits()

int GetDIBits(
	HDC hdc ,
	HBITMAP hbmp ,
	UINT uStartScan ,
	UINT cScanLines ,
	LPVOID lpvBits ,
	LPBITMAPINFO lpbi ,
	UINT uUsage
);
指定されたビットマップのビットを取得し、指定された形式でバッファにコピーします

hdc - デバイスコンテキストのハンドルを指定します
hbmp - 変換するビットマップのハンドルを指定します
uStartScan - 取得する最初の走査線を指定します
cScanLines - 走査線の数を指定します
lpvBits - 変換したビットマップデータを受けるバッファを指定します
lpbi - 希望する形式を指定する BITMAPINFO 構造体へのポインタを指定します
uUsage - カラー情報フラグを指定します

fuUsage には次の定数のいずれかを指定します

定数解説
DIB_PAL_COLORS カラーテーブルに論理パレットに対する
16 ビットインデックスが格納されている
DIB_RGB_COLORS カラーテーブルに RGB 値が格納されている



前のページへ戻る次のページへ