EternalWindows
ビットマップ / ビットマップのロード

GDIによる描画といえば、RectangleやEllipseといった図形を描画する関数を呼び出すイメージがありますが、 こうした関数を呼び出すだけでは高度なグラフィックスを描画するのは難しいものがあります。 一般的に、図形や直線を描画するのはペイントツールであり、 アプリケーションが必要とするのはこうしたペイントツールで作成したビットマップになると思われますから、 ビットマップを描画する方法を理解しておくことは重要といえます。 また、アプリケーションがPNGやJPEGを扱うような場合でも、 最終的にはこれらをビットマップに変換して描画することになります。

ビットマップを描画するには、まずビットマップをロードする必要があります。 これには、LoadImageを呼び出します。

HANDLE LoadImage(
  HINSTANCE hinst,
  LPCTSTR lpszName,
  UINT uType,
  int cxDesired,
  int cyDesired,
  UINT fuLoad
);

hinstは、インスタンスハンドルを指定します。 ファイルからイメージをロードするときにはNULLで構いません。 lpszNameは、ロードするイメージの名前を指定します。 ビットマップをロードするときは、ビットマップのファイル名を指定します。 uTypeは、ロードするイメージのタイプを指定します。 LoadImageはビットマップ以外にアイコンやカーソルをロードする機能も備えているため、 ロードするタイプを関数に伝える必要があります。 cxDesiredとcyDesiredは、0で問題ありません。 fuLoadは、イメージをロードするときの詳細情報を指定します。 関数が成功すると指定したタイプのハンドルが返ります。

ここで、LoadImageの使い方を見てみましょう。 カレントディレクトリに存在するsample.bmpをロードするものとします。

HBITMAP hbmp;

hbmp = (HBITMAP)LoadImage(NULL, TEXT("sample.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

第3引数のタイプはビットマップをロードするときは、IMAGE_BITMAPを指定します。 HBITMAPという型は、ビットマップというGDIオブジェクトを識別します。 キャストが必要なのは、LoadImageがアイコンなどのロードにも対応しているため、 戻り値をHBITMAPという型に限定していないからです。 ファイルからビットマップをロードする場合は、第6引数にLR_LOADFROMFILEを指定します。

GDI内部では、ビットマップはGDIオブジェクトとして扱われます。 GDIオブジェクトの属性は、GetObjectで取得することができます。

int GetObject(
  HGDIOBJ hgdiobj,
  int cbBuffer,
  LPVOID lpvObject
);

hgdiobjは、GDIオブジェクトのハンドルを指定します。 cbBufferは、lpvObjectのサイズを指定します。 lpvObjectは、属性を受け取る構造体のアドレスを指定します。

GetObjectの第1引数にビットマップのハンドルを指定した場合、 第3引数にBITMAP構造体のアドレスを指定します。

typedef struct tagBITMAP {
  LONG   bmType; 
  LONG   bmWidth; 
  LONG   bmHeight; 
  LONG   bmWidthBytes; 
  WORD   bmPlanes; 
  WORD   bmBitsPixel; 
  LPVOID bmBits; 
} BITMAP, *PBITMAP; 

bmTypeは、0が格納されます。 bmWidthは、ビットマップの幅が格納されます。 bmHeightは、ビットマップの高さが格納されます。 bmWidthBytesは、ビットマップの一行に要しているサイズが格納されます。 bmPlanesは、ビットマップのプレーン数が格納されます。 通常は、1になります。 bmBitsPixelは、ビットマップの1ピクセルを表すのに使用しているビット数が格納されます。 bmBitsは、ビットマップのピクセルを維持するビットイメージへのアドレスが格納されます。

LoadImageの第6引数にLR_CREATEDIBSECTIONを指定していない場合は、 BITMAP構造体のbmWidthBytesとbmBitsPixel、及びbmBitsを参照するべきではありません。 理由は、ビットマップがデバイス固有のDDBという形で扱われることになり、 本来のビットマップの情報を正確に参照できなくならかです。 たとえば、ビット数が24であるビットマップはbmBitsPixelに24が格納されていると予想しますが、 LR_CREATEDIBSECTIONを指定していない場合は、32という値が格納されていることがあります。 DDBやDIBについては、後の節で詳しく取り上げます。

今回のプログラムはビットマップをロードし、 マウスの左ボタンが押されたときに幅と高さを表示します。

#include <windows.h>

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;

	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;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 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 hbmp = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("sample.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
		return hbmp != NULL ? 0 : -1;

	case WM_LBUTTONDOWN: {
		TCHAR  szBuf[256];
		BITMAP bm;

		GetObject(hbmp, sizeof(BITMAP), &bm);

		wsprintf(szBuf, TEXT("幅 %d 高さ %d"), bm.bmWidth, bm.bmHeight);
		MessageBox(hwnd, szBuf, TEXT("ビットマップの属性"), MB_OK);

		return 0;
	}

	case WM_DESTROY:
		if (hbmp != NULL)
			DeleteObject(hbmp);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

ビットマップをロードするという作業は、一度だけ行うのが基本です。 描画の度にロードしていては時間がかかりますし、 何よりこのような初期化コードは、WM_CREATEに集中させたいものです。

case WM_CREATE:
	hbmp = (HBITMAP)LoadImage(NULL, TEXT("sample.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	return hbmp != NULL ? 0 : -1;

ファイルが本当に存在するかはプログラムを実行してみないと分からないため、 LoadImageの戻り値は必ずチェックをするようにします。

ビットマップはGDIオブジェクトであるため、削除はDeleteObjectで行います。

case WM_DESTROY:
	if (hbmp != NULL)
		DeleteObject(hbmp);
	PostQuitMessage(0);
	return 0;

hbmpがNULLでないかのチェックは重要です。 WM_CREATEで失敗したことによって、WM_DESTROYが送られたならば、 そのときにはビットマップがロードされていないわけですから、 削除を実行するわけにはいきません。


戻る