EternalWindows
リージョン / クリッピングリージョン

前節では、リージョンを使用して長方形を描画しましたが、 このような描画目的でリージョンを使用するならば、 最初からRectangleで描画をしたほうがよいともいえます。 そこで今回は、リージョンをクリッピングリージョンという角度から捉えたいと思います。 クリッピングリージョンとは、デバイスコンテキストに対して描画できる範囲のことであり、 リージョンをデバイスコンテキストに選択すると、 そのリージョンはデバイスコンテキストのクリッピングリージョンになります。 リージョンは、GDIオブジェクトであるためSelectObjectで選択することができますが、 SelectClipRgnという専用の関数でも選択することができます。

int SelectClipRgn(
  HDC hdc,
  HRGN hrgn
);

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

リージョンを作成する関数はCreateRectRgnの他に、CreateEllipticRgnがあります。 この関数は、楕円のリージョンを作成します。

HRGN CreateEllipticRgn(
  int nLeftRect,
  int nTopRect,
  int nRightRect,
  int nBottomRect
);

nLeftRectは、楕円に外接する長方形の左上隅のx座標を指定します。 nTopRectは、楕円に外接する長方形の左上隅のy座標を指定します。 nRightRecは、楕円に外接する長方形の右下隅のx座標を指定します。 nBottomRectは、楕円に外接する長方形の右下隅のy座標を指定します。

リージョンは、図形だけでなく図形の位置も定義しています。 この位置を移動させたい場合は、OffsetRgnを呼び出します。

int OffsetRgn(
  HRGN hrgn,
  int nXOffset,
  int nYOffset
);

hrgnは、リージョンのハンドルを指定します。 nXOffsetは、x座標への移動方向を指定します。 nYOffsetは、y座標への移動方向を指定します。

今回のプログラムは、クリッピングリージョンを設定した状態でビットマップを描画します。 また、マウスの左ボタンが押された場合は、クリッピングを行わずにビットマップを描画します。 カレントディレクトリにsample.bmpを用意しておいてください。

#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 BOOL    bCliping = TRUE;
	static HRGN    hrgn = NULL;
	static HDC     hdcMem = NULL;
	static HBITMAP hbmpMem = NULL;
	static HBITMAP hbmpMemPrev = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HDC hdc;
		
		hrgn = CreateEllipticRgn(0, 0, 300, 300);
		if (hrgn == NULL)
			return -1;

		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_LBUTTONDOWN:
		bCliping = !bCliping;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;
	
	case WM_KEYDOWN: {
		int  dx, dy;

		if (!bCliping)
			return 0;

		dx = dy = 0;

		switch (wParam) {
		case VK_LEFT:   dx = -10; break;
		case VK_UP:     dy = -10; break;
		case VK_RIGHT:  dx = +10; break;
		case VK_DOWN:   dy = +10; break;
		default: return 0;
		}

		OffsetRgn(hrgn, dx, dy);
		
		InvalidateRect(hwnd, NULL, TRUE);

		return 0;
	}

	case WM_PAINT: {
		HDC         hdc;
		BITMAP      bm;
		PAINTSTRUCT ps;
		
		GetObject(hbmpMem, sizeof(BITMAP), &bm);

		hdc = BeginPaint(hwnd, &ps);

		if (bCliping)
			SelectClipRgn(hdc, hrgn);

		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);
		}
		
		if (hrgn != NULL)
			DeleteObject(hrgn);
		
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

bClipingがTRUEである場合は、WM_PAINTでBitBltを呼び出す前にSelectClipRgnが呼び出されます。 これにより、表示されるビットマップは、設定されたクリッピングリージョン内のみとなります。 bClipingは、WM_LBUTTONDOWNで変更されることになります。 InvalidateRectの第3引数にTRUEを指定しているため、 クライアント領域の内容が消去されてWM_PAINTが発行されることになります。

今回のプログラムには、リージョンを移動させるコードが含まれています。 リージョンを移動させるとクリッピングリージョンの位置が変わるわけですから、 ビットマップの別の場所が見えることになります。

case WM_KEYDOWN: {
	int  dx, dy;

	if (!bCliping)
		return 0;

	dx = dy = 0;

	switch (wParam) {
	case VK_LEFT:   dx = -10; break;
	case VK_UP:     dy = -10; break;
	case VK_RIGHT:  dx = +10; break;
	case VK_DOWN:   dy = +10; break;
	default: return 0;
	}

	OffsetRgn(hrgn, dx, dy);
	
	InvalidateRect(hwnd, NULL, TRUE);

	return 0;
}

WM_KEYDOWNは、キーボードのキーが押されたら送られるメッセージで、 押したキーはWPARAMから参照することができます。 OffsetRgnは、第2引数と第3引数で指定された分だけ、 第1引数のリージョンを移動させます。 その効果を直ちに知覚できるよう、InvalidateRectでWM_PAINTを発行します。


戻る