クリッピング


指定範囲のクリッピング

クリッピングとは、特定範囲の矩形内部のみを描画対象とするテクニックで
GDI の描画命令は、例え指定範囲が対象矩形の外を指定していても
クリッピングさえしていれば、その外に描画されるようなことがなくなります

代表的にいうならば、ウィンドウそのものがクリップ領域でもあり
手段が無いわけではないが、ウィンドウ領域を越えて描画範囲を指定しても
プログラムはウィンドウの外にまでラインを引いたりすることはできません
これは、BeginPaint() 等が返すデバイスコンテキストが
デフォルトでウィンドウのクライアント領域ををクリッピングしているからです

プログラムは必要であれば、リージョンを使ってクリッピングすることができます
リージョンをデバイスコンテキストのクリッピングに設定するには
SelectClipRgn() 関数を用います

int SelectClipRgn(HDC hdc , HRGN hrgn);

hdc にはデバイスコンテキストのハンドル
hrgn には、クリッピングの矩形情報を表すリージョンのハンドルを指定します
関数は、現在のクリッピングの状態を表す定数を返します
この定数については、下記のリファレンスを参照してください

この関数を指定した後は、hrgn のリージョンの範囲よりも外に描画することはできません
これで、論理的な描画矩形を建築することができるのです
クリッピング領域を解除するには、hrgn 引数に NULL を指定すれば解除されます

因みに、現在のクリッピング領域と論理演算を行う形で
より柔軟な指定を実現した拡張関数 ExtSelectClipRgn() もあります

int ExtSelectClipRgn(HDC hdc , HRGN hrgn , int fnMode);

第二引数までと戻り値は SelectClipRgn() 関数と同じです
fnMode には実行される操作モードを定数で指定します
ここで指定する定数は CombineRgn() の fnCombineMode で指定する定数と同じです
より複雑なクリッピング範囲を指定する場合は、こちらを使うと良いでしょう
#include <windows.h>

#define TITLE TEXT("Kitty on your lap")

BITMAPINFO bmpInfo;
BYTE * bPixelBits;

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

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		GetClientRect(hWnd , &rect);
		hRgn = CreateEllipticRgn(0 , 0 , rect.right , rect.bottom);
		SelectClipRgn(hdc , hRgn);
		SetDIBitsToDevice(
			hdc , 0 , 0 ,
			bmpInfo.bmiHeader.biWidth , bmpInfo.bmiHeader.biHeight ,
			0 , 0 , 0 , bmpInfo.bmiHeader.biHeight ,
			bPixelBits , &bmpInfo , DIB_RGB_COLORS
		);

		SelectClipRgn(hdc , NULL);
		DeleteObject(hRgn);
		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;
	BITMAPFILEHEADER bmpFileHeader;

	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") , TITLE ,
			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;
}


このプログラムは、コマンドライン引数で指定したフルカラーの Windows DBI を
(これまでのように、簡単のためエラーチェックなどを行っていません)
その DIB の長方形に外接した円でクリッピングして描画しています

上の図を見てわかるように、描画される DIB は円でクリッピングされています
クリッピングの用途は様々ですが、とくに恩恵を受けるのはアニメーション処理です
高度な動画は(パレットアニメを除いて)クライアント領域を短い時間に連続して更新します
この時、動く部分がウィンドウの一部だけならば、その部分だけをクリッピングし
動く矩形ないのみを更新すれば、全体を更新するよりも速度の向上を図れます
Java VM 等の動作速度がシビアなグラフィックス分野では、特に重宝されているテクニックです

また、現在のクリッピング領域を取得する方法もあります
方法は2つで、クリッピング領域となっている最小の長方形を取得する方法と
現在のクリッピング領域となっているリージョンをえる方法があります

長方形を得るには GetClipBox() 関数を使用します

int GetClipBox(HDC hdc , LPRECT lprc);

hdc にはデバイスコンテキストのハンドルを
lprc にはクリッピング領域の長方形を格納する RECT 構造体へのポインタを指定します
関数の戻り値は SelectClipRgn() 関数と同じです

より確実に、正確にクリッピング領域の範囲を取得したい場合は
リージョンで取得する GetClipRgn() 関数を用いるべきです

int GetClipRgn(HDC hdc , HRGN hrgn);

hdc には、デバイスコンテキストのハンドルを
hrgn は、クリッピングリージョンを受け取る既存の有効なリージョンを指定します
このリージョンは、クリッピングリージョンのコピーとなります
そのため、ここで受け取ったリージョンを変更しても、クリッピング領域は変わりません

デバイスコンテキストにクリッピング領域がなければ 0 が
クリッピング領域があれば 1 を返します
#include <windows.h>

#define TITLE TEXT("Kitty on your lap")

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

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		hRgn1 = CreateEllipticRgn(10 , 10 , 100 , 100);
		hRgn2 = CreateEllipticRgn(50 , 50 , 200 , 150);
		CombineRgn(hRgn1 , hRgn1 , hRgn2 , RGN_OR);
		SelectClipRgn(hdc , hRgn1);

		GetClipBox(hdc , &rect);
		GetClipRgn(hdc , hRgn2);

		SelectClipRgn(hdc , NULL);
		DeleteObject(hRgn1);

		Rectangle(hdc , rect.left , rect.top , rect.right , rect.bottom);
		FillRgn(hdc , hRgn2 , GetStockObject(BLACK_BRUSH));

		DeleteObject(hRgn2);
		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;

	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") , TITLE ,
			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;
}


このプログラムは、最初にクリッピング領域をリージョンで作りだし
その後、長方形とリージョンで現在のクリッピング領域の範囲を受け取ります
そして、クリッピングを解除して取得した範囲を図として描画しています

長方形で取得した場合は、クリッピング領域に対して
最も最小となる長方形の範囲として取得されていることが確認できます


SelectClipRgn()

int SelectClipRgn(HDC hdc , HRGN hrgn);

クリッピング領域を指定します

hdc - デバイスコンテキストのハンドルを指定します
hrgn - クリッピングの矩形情報を表すリージョンのハンドルを指定します

戻り値 - 現在のクリッピング領域の状態を表す定数

戻り値は、次の定数のいずれかになります

定数解説
NULLREGION クリッピング領域は空です
SIMPLEREGION クリッピング領域は単一の長方形です
COMPLEXREGION クリッピング領域は単一の長方形よりも複雑な形です
ERROR エラーが発生しました
(以前のクリッピング領域に影響はありません)

ExtSelectClipRgn()

int ExtSelectClipRgn(HDC hdc , HRGN hrgn , int fnMode);

クリッピング領域を指定します

hdc - デバイスコンテキストのハンドルを指定します
hrgn - クリッピングの矩形情報を表すリージョンのハンドルを指定します
fnMode - 実行される操作モードを定数で指定します

戻り値 - 現在のクリッピング領域の状態を表す定数

fnMode には次の定数のいずれかを指定できます

定数解説
RGN_AND 現在のクリッピング領域と、hrgn パラメータで指定した
リージョンの両方に含まれる領域 (重複する領域) を
新しいクリッピング領域にします
RGN_COPY hrgn パラメータで指定した
リージョンのコピーを、新しいクリッピング領域にします
この動作は、SelectClipRgn 関数と同じです
hrgn パラメータが NULL のときは
デフォルトの領域が新しいクリッピング領域になります
RGN_DIFF 現在のクリッピング領域から、hrgn パラメータで指定した
リージョンを除いた領域を
新しいクリッピング領域にします
RGN_OR 現在のクリッピング領域と、hrgn パラメータで指定した
リージョンを組み合わせた領域を
新しいクリッピング領域にします
RGN_XOR 現在のクリッピング領域と、hrgn パラメータで指定した
リージョンを組み合わせた領域から
その 2 つのリージョンの両方に含まれるリージョンを除いた領域を
新しいクリッピング領域にします

戻り値は、次の定数のいずれかになります

定数解説
NULLREGION クリッピング領域は空です
SIMPLEREGION クリッピング領域は単一の長方形です
COMPLEXREGION クリッピング領域は単一の長方形よりも複雑な形です
ERROR エラーが発生しました
(以前のクリッピング領域に影響はありません)

GetClipBox()

int GetClipBox(HDC hdc , LPRECT lprc);

クリッピング領域の最小の長方形を取得します

hdc - デバイスコンテキストのハンドルを指定します
lprc - クリッピング領域の長方形を格納する RECT 構造体へのポインタを指定します

戻り値 - 現在のクリッピング領域の状態を表す定数

戻り値は、次の定数のいずれかになります

定数解説
NULLREGION クリッピング領域は空です
SIMPLEREGION クリッピング領域は単一の長方形です
COMPLEXREGION クリッピング領域は単一の長方形よりも複雑な形です
ERROR エラーが発生しました
(以前のクリッピング領域に影響はありません)

GetClipRgn()

int GetClipRgn(HDC hdc , HRGN hrgn);

現在のクリッピングリージョンを取得します

hdc - デバイスコンテキストのハンドルを指定します
hrgn - クリッピングリージョンを受け取る既存の有効なリージョンを指定します

戻り値 - クリッピング領域がなければ 0。あれば 1



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