EternalWindows
ビットマップ / メモリデバイスコンテキスト

ビットマップを描画するには、まずそのビットマップをSelectObjectで メモリデバイスコンテキストに選択しなければなりません。 通常のデバイスコンテキストがウインドウに関連付けられているのに対し、 メモリデバイスコンテキストはメモリ上のビットマップに関連付けられ、 メモリデバイスコンテキストを通常のデバイスコンテキストにコピーすると、 そのメモリデバイスコンテキストに関連付けられているビットマップが描画されることになります。 メモリデバイスコンテキストは、CreateCompatibleDCで作成します。

HDC CreateCompatibleDC(
  HDC hdc
);

hdcは、作成するメモリデバイスコンテキストと互換性を持たせたい デバイスコンテキストのハンドルを指定します。 これは、メモリデバイスコンテキストを将来、デバイスコンテキストにコピーするうえで、 両者に互換性が無いために描画が失敗してしまうようなことを防ぐためです。 戻り値は、メモリデバイスコンテキストのハンドルです。

不要になったメモリデバイスコンテキストは、DeleteDCで削除することになります。

BOOL DeleteDC(
  HDC hdc
);

hdcは、デバイスコンテキストのハンドルを指定します。

メモリデバイスコンテキストはデフォルトとして、 1 × 1 の大きさを持ったモノクロビットマップが割り当てられています。 このメモリデバイスコンテキストをコピーしても、 黒いピクセルが1つ表示されるだけです。 しかし、ロードしたビットマップを選択すれば、 メモリデバイスコンテキストとそのビットマップは関連付けられます。 結果的に、選択したビットマップの内容が描画されることになります。

SelectObject(hdcMem, hbmpMem);

これにより、ビットマップがメモリデバイスコンテキストに選択されます。 後は、メモリデバイスコンテキストをウインドウに関連付けられたデバイスコンテキストにコピーすれば、 ビットマップはウインドウに描画されることになります。

デバイスコンテキストの内容をコピーするには、BitBltを呼び出します。 このコピーは、ビットブロックの転送と呼ばれることもあります。

BOOL BitBlt(
  HDC hdcDest,
  int nXDest,
  int nYDest,
  int nWidth,
  int nHeight,
  HDC hdcSrc,
  int nXSrc,
  int nYSrc,
  DWORD dwRop
);

hdcDestは、コピー先のデバイスコンテキストのハンドルを指定します。 nXDestは、コピー先のx座標を指定します。 nYDestは、コピー先のy座標を指定します。 nWidthは、コピーする幅を指定します。 nHeightは、コピーする高さを指定します。 hdcSrcは、コピー元のデバイスコンテキストのハンドルを指定します。 nXSrcは、nXSrcはコピー元の開始位置を示すx座標を指定します。 nYSrcは、nYSrcはコピー元の開始位置を示すy座標を指定します。 dwRopは、ラスタオペレーションに関する定数を指定することができますが、 主としてSRCCOPYを指定することになるでしょう。 この定数は、コピー元の内容をコピー先にそのままコピーするという意味を持ちます。

今回のプログラムは、ロードしたビットマップをBitBltで描画します。

#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 HDC     hdcMem = NULL;
	static HBITMAP hbmpMem = NULL;
	static HBITMAP hbmpMemPrev = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		HDC hdc;

		hdc = GetDC(hwnd);
		
		hdcMem = CreateCompatibleDC(hdc);
		hbmpMem = (HBITMAP)LoadImage(NULL, TEXT("sample.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
		if (hbmpMem == NULL) {
			ReleaseDC(hwnd, hdc);
			return -1;
		}

		hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);

		ReleaseDC(hwnd, hdc);

		return 0;
	}

	case WM_PAINT: {
		HDC         hdc;
		BITMAP      bm;
		PAINTSTRUCT ps;

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

		hdc = BeginPaint(hwnd, &ps);
		
		BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

		EndPaint(hwnd, &ps);

		return 0;
	}

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

	default:
		break;

	}

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

静的変数のhdcMemは、メモリデバイスコンテキストのハンドルです。 ここにhbmpMemをSelectObjectで選択することになります。 通常のデバイスコンテキストであれ、今回のようなメモリデバイスコンテキストであれ、 それを開放するような場合は元の状態に戻してからのほうが好ましいため、 以前のビットマップを保存するための変数(hbmpMemPrev)が必要となります。

WM_CREATEではビットマップのロードの他に、メモリデバイスコンテキストの作成と ビットマップの選択に関するコードが追加されています。

case WM_CREATE: {
	HDC hdc;

	hdc = GetDC(hwnd);
	
	hdcMem = CreateCompatibleDC(hdc);
	hbmpMem = (HBITMAP)LoadImage(NULL, TEXT("sample.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	if (hbmpMem == NULL) {
		ReleaseDC(hwnd, hdc);
		return -1;
	}

	hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);

	ReleaseDC(hwnd, hdc);

	return 0;
}

CreateCompatibleDCの第1引数は将来、コピー先となるデバイスコンテキストです。 プログラムではウインドウに関連付けられているデバイスコンテキストにコピーするので、 GetDCで取得したデバイスコンテキストを指定します。

破棄のコードは、以下のようになっています。

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

まず、メモリデバイスコンテキストが作成されているかを確認します。 作成されているならば、それをDeleteDCで削除する必要があります。 このとき、ビットマップが作成されているならば、 元のビットマップに置き換えてから削除を行います。

WM_PAINTでは、メモリデバイスコンテキストをBeginPaintで得られた デバイスコンテキストにコピーします。 このデバイスコンテキストはウインドウに関連付けられているため、 ビットマップが実際にウインドウに描画されることになります。

BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

BitBltの第4引数と第5引数はメモリデバイスコンテキストをコピーする範囲です。 普通はビットマップの全体を描画するでしょうから、 ビットマップの幅と高さを単純に指定すればよいでしょう。 なお、この第4引数と第5引数が、ビットマップの右下隅の座標を 要求しているわけではないことに注意してください。 たとえば、長方形を(x, y)の位置に描画しようとしたときは、 Rectangle関数を次のように呼び出していました。

// 長方形の幅は30、高さは50にしたいとする
Rectangle(hdc, x, y, x + 30, y + 50);

これは、Rectangleの第3引数や第4引数が長方形の幅と高さを要求せずに、 最終的な右下隅の座標を要求しているため、xやyを足すことになるのです。 しかし、BitBltは常に幅と高さという形で引数を要求しているので、 x座標やy座標を足す必要はありません。 もし、ビットマップの位置をRECT構造体で表していたとするならば、 以下のようにBitBltを呼び出すことになるでしょう。

BitBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.buttom - rc.top, hdcMem, 0, 0, SRCCOPY);

leftとtopは左上隅でrightとbuttomは右下隅です。 これらの差分を取れば長方形の幅や高さを算出できます。


戻る