DIB の表示


DIB をデバイスに描画

以前、「ビットマップの表示」で DDB をウィンドウに描画しました
今回は、ディスクから読み出した DIB の描画に挑戦してみましょう

DIB のピクセルビットを取得して、SetPixel() 関数でそれを描画する実験を行いました
同様の方法で、メモリデバイスコンテキストに描画すれば DDB に変換することになりますが
画像ビューワを作成している人が、全てそのような作業をしているわけではありません
情報ヘッダを分析し、圧縮なども含めて柔軟に対応するには相当量の技術が必要です

Win32 API では、情報ヘッダとピクセルビットへのポインタがあれば
あとは SetDIBitsToDevice() 関数がデバイスに表示してくれます
int SetDIBitsToDevice(
	HDC hdc ,
	int XDest , int YDest,
	DWORD dwWidth , DWORD dwHeight ,
	int XSrc , int YSrc ,
	UINT uStartScan , UINT cScanLines ,
	CONST VOID *lpvBits , CONST BITMAPINFO *lpbmi ,
	UINT fuColorUse
);
hdc には、DIB をコピーするデバイスコンテキストを指定します
XDest にはコピー先の X 座標、YDest には Y 座標を指定します
この論理座標はデバイスコンテキストの座標なので、DIB とは異なりトップダウンです
つまり、矩形の左上隅が座標の原点となります

dwWidth は DIB の幅、dwHeight には高さを
XSrc には DIB の X 座標、YSrc には Y 座標をそれぞれ指定します
DIB の座標は基本的にボトムアップなので、左下隅の座標となります

uStartScan は DIB の最初の走査線を指定します
走査線とはディスプレイの用語であり、グラフィックスでは「行」と言ってもかまいません
一般的には、コピーを開始する行は最初の行で良いので 0 を指定します

cScanLines は描画するべき走査線の数を指定します
これも、つまるところ描画する行数をピクセル単位で指定するということです
一般的には、最後の行まで描画することが目的なのでイメージの高さを指定します

lpvBits に DIB のピクセルビットの配列の先頭へのポインタを指定します
lpbmi は DIB に関連する情報が格納された BITMAPINFO 構造体へのポインタです

fuColorUse はカラー情報を表すフラグを指定します
RGB 値が直接カラーテーブルに含まれているのならば DIB_RGB_COLORS
論理パレットのインデックスならば DIB_PAL_COLORS を指定します
パレットは後ほど詳しく説明することにするので、今は DIB_RGB_COLORS を指定してください
#include <windows.h>

BITMAPFILEHEADER bmpFileHeader;
BITMAPINFO bmpInfo;
BYTE * bPixelBits;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;

	switch (msg) {
	case WM_DESTROY:
		free(bPixelBits);
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		SetDIBitsToDevice(
			hdc , 0 , 0 ,
			bmpInfo.bmiHeader.biWidth , bmpInfo.bmiHeader.biHeight ,
			0 , 0 , 0 , bmpInfo.bmiHeader.biHeight ,
			bPixelBits , &bmpInfo , DIB_RGB_COLORS
		);
		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;
	}

	ReadFile(hFile , &bmpInfo , sizeof (BITMAPINFOHEADER) , &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;
}
さて、このプログラムはコマンドラインで指定した Windows 版の24ビット DIB を
ウィンドウの原点から描画するという単純なビットマップビューワです

プログラムを簡単に表現するため、フルカラー DIB にしか対応しておらず
BITMAPINFO 型変数もスタックに配置し、静的に対処する方法をとっています
そのため、DIB の読み込みはとてもスマートとは言えませんが
フルカラーの Windows DIB であれば、問題なく表示できるようになっています

SetDIBitsToDevice() 関数は、DIB のボトムアップの影響に注意してください
指定するソースの原点、つまりコピー元の DIB の原点はボトムアップです
X 座標の考え方は今と同じですが、Y 座標は下から上に向かっていくのです
例えば、上のプログラムの SetDIBitsToDevice() を次のように改良してください
SetDIBitsToDevice(
	hdc , 0 , 0 ,
	bmpInfo.bmiHeader.biWidth , bmpInfo.bmiHeader.biHeight / 2 ,
	0 , bmpInfo.bmiHeader.biHeight / 2 ,
	0 , bmpInfo.bmiHeader.biHeight ,
	bPixelBits , &bmpInfo , DIB_RGB_COLORS
);
この関数は、コピーする DIB の Y 座標の始点をイメージの中央に指定しています
トップダウンの概念では、画像の中央より下をコピーしたいという意思になります
しかし、ボトムアップの場合は下から上へと座標が進むのだから
この関数は、DIB の中央より上をデバイスコンテキストにコピーするでしょう


逐次描画

SetDIBitsToDevice() 関数には、描画する走査線を指定することができました
実は、これを指定することでシーケンシャルな描画を可能とします

例えば、メモリが少なく DIB 全体をメモリに読み込めない場合などは
ディスクからピクセル数行文を読み込んで描画するという方法が考えられます
これを繰り返せば、最終的には一つのビットマップとして描画させることができます
この方法は、描画のみを目的とする DIB を最小のメモリ空間で実現する最良の方法だ

また、低速度のネットワークなどでもこの方法は有効となる
非常の転送速度の遅い記録装置やネットワークから DIB を取得して描画する場合
DIB の全てのピクセルビットを取得してから表示するという方法もあるが
取得したピクセルを徐々に描画するという非同期表示の方がユーザーには喜ばれるだろう

このような処理をしたい場合、uStartScan に現在の走査線(行)の位置を示す値を
cScanLines に描画することが可能な走査線の数を指定します
uStartScan に 10 と指定すれば、lpvBits の先頭ピクセルは 10 行目であることを示し
cScanLines に 10 と指定すれば、lpvBits から 10 行分をデバイスにコピーする

この方法で for ループなどを用い、ピクセルの取得と描画を繰り返せば
徐々にデバイスに DIB をコピーすることが可能となる
#include <windows.h>

HANDLE hFile;
DWORD dwBytes;

BITMAPFILEHEADER bmpFileHeader;
BITMAPINFO bmpInfo;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	int iScan = 1;

	static BYTE * bPixelBits;	
	const int WIDTH = 4 *
		((bmpInfo.bmiHeader.biWidth * bmpInfo.bmiHeader.biBitCount + 31)/ 32);

	switch (msg) {
	case WM_DESTROY:
		CloseHandle(hFile);
		free(bPixelBits);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		bPixelBits =  (BYTE *) malloc (WIDTH);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);

		for (; iScan <= bmpInfo.bmiHeader.biHeight ; iScan++) {
			ReadFile(hFile , bPixelBits , WIDTH , &dwBytes , NULL);
			SetDIBitsToDevice(
				hdc , 0 , 0 ,
				bmpInfo.bmiHeader.biWidth , bmpInfo.bmiHeader.biHeight ,
				0 , 0 , iScan , 1 ,
				bPixelBits , &bmpInfo , DIB_RGB_COLORS
			);
		}

		SetFilePointer(
			hFile , 
			sizeof (BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) ,
			NULL , FILE_BEGIN
		);

		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;

	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;
	}

	ReadFile(hFile , &bmpInfo , sizeof (BITMAPINFOHEADER) , &dwBytes , NULL);

	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_THICKFRAME  | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			bmpInfo.bmiHeader.biWidth ,
			bmpInfo.bmiHeader.biHeight  + GetSystemMetrics(SM_CYCAPTION) ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

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

	return msg.wParam;
}
このプログラムは、先ほどのプログラム同様に
コマンドライン引数で指定したビットマップを表示するというものですが
ビットマップの描画に必要とするメモリは、わずかビットマップ1行分のメモリです

当然、このプログラムを実行すればわかるように実行速度は低下します
しかし、巨大なサイズのビットマップもわずか1行分のメモリで描画できるというのは
メモリに制限のある環境やソフトウェアでは、それなりの魅力となるでしょう


SetDIBitsToDevice()

int SetDIBitsToDevice(
	HDC hdc ,
	int XDest , int YDest,
	DWORD dwWidth , DWORD dwHeight ,
	int XSrc , int YSrc ,
	UINT uStartScan , UINT cScanLines ,
	CONST VOID *lpvBits , CONST BITMAPINFO *lpbmi ,
	UINT fuColorUse
);
DIB を指定したデバイスコンテキストにコピーします

hdc - コピー先のデバイスコンテキストを指定します
XDest - コピー先の X 座標を指定してます
YDest - コピー先の Y 座標を指定します
dwWidth - DIB の幅を指定します
dwHeight - DIB の高さを指定します
XSrc - DIB の X 座標を指定します
YSrc - DIB の Y 座標を指定します
uStartScan - コピーするピクセルビットの走査線位置を指定します
cScanLines - コピーする走査線数を指定します
lpvBits - ピクセルビットへのポインタを指定します
lpbmi - DIB 情報のポインタを指定します
fuColorUse - カラー情報フラグを指定します

戻り値 - 設定された走査線数。失敗した時は 0

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

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



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