パス


グラフィックスパス

基礎編のグラフィックスの解説で「リージョン」の使い方を説明しましたが
Win16 から Win32 に移行した時に、さらに新しい概念「パス」が追加されました

パスは、リージョンに類似した図形のコレクションです
ただし、リージョンとは異なりデバイスコンテキストの属性として操作することができます
パスを指定したデバイスコンテキストは、描画命令をパスとして保存するようになり
パスを終了するまで、描画命令が実際のデバイスに描画されることはありません

パスを開始するには BeginPath() 関数を用い
パスを終了するには EndPath() 関数を用います

BOOL BeginPath(HDC hdc);
BOOL EndPath(HDC hdc);

両方とも、hdc にはデバイスコンテキストのハンドルを指定します
戻り値は、関数が成功すれば 0 以外、失敗すれば 0 です

この間に指定した描画関数は、デバイスには反映されず
GDI の内部パスとして保存され、パスが破棄されるまでそれが生き続けます
パスは EndPath() ではなく、BeginPath() が呼び出されるまで有効です

たとえば、保存されたパスを塗りつぶしたり、輪郭だけをペンで描画することができます
パスの輪郭を描画するには StrokePath() 関数を使います

BOOL StrokePath(HDC hdc);

hdc には描画するデバイスコンテキストのハンドルを指定します
戻り値は、関数が成功すれば 0 以外、失敗すれば 0 です
#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;
	HFONT hFont;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		BeginPath(hdc);

		SetBkMode(hdc , TRANSPARENT);
		hFont = CreateFont(
			80 , 0 , 0 , 0 , FW_BOLD , FALSE , FALSE , FALSE ,
			ANSI_CHARSET , OUT_DEFAULT_PRECIS ,
			CLIP_DEFAULT_PRECIS , DEFAULT_QUALITY , 
			0 , NULL
		);
		SelectObject(hdc , hFont);
		TextOut(hdc , 0 , 0 , TITLE , lstrlen(TITLE));

		EndPath(hdc);
		StrokePath(hdc);

		SelectObject(hdc , GetStockObject(SYSTEM_FONT));
		DeleteObject(hFont);
		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;
}


このプログラムは、パスを開始してから
大きなフォントに変更して TextOut() 関数で文字を描画しています
しかし、この時点ではパスに記録されただけで、デバイスには表示されません

EndPath() でパスを終了した後、StrokePath() 関数を用い
現在選択されているペンで、パスの輪郭を描画しています
SetBkMode() を使っているのは、文字の背景を塗りつぶした場合は
それもパスに記録され、背景の矩形も StrokePath() でなぞられてしまうからです

このようにパスは性質がリージョンに似ていますが、比較的リージョンよりも柔軟です
パスを塗りつぶすには FillPath() 関数を使います
これを使えば、現在のブラシと多角形モードでパスを塗りつぶします

BOOL FillPath(HDC hdc);

hdc にはパスが入っているデバイスコンテキストを指定します
関数が成功すると 0 以外、失敗すると 0 が返ります
#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;
	HFONT hFont;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		BeginPath(hdc);

		SetBkMode(hdc , TRANSPARENT);
		hFont = CreateFont(
			80 , 0 , 0 , 0 , FW_BOLD , FALSE , FALSE , FALSE ,
			ANSI_CHARSET , OUT_DEFAULT_PRECIS ,
			CLIP_DEFAULT_PRECIS , DEFAULT_QUALITY , 
			0 , NULL
		);
		SelectObject(hdc , hFont);
		TextOut(hdc , 0 , 0 , TITLE , lstrlen(TITLE));

		EndPath(hdc);
		SelectObject(hdc , CreateHatchBrush(HS_DIAGCROSS , 0xFF));
		FillPath(hdc);

		SelectObject(hdc , GetStockObject(SYSTEM_FONT));
		DeleteObject(hFont);
		DeleteObject(SelectObject(hdc , GetStockObject(WHITE_BRUSH)));
		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;
}


今度は、パスの輪郭の内部が設定したハッチブラシで塗りつぶされていますね
例えば、ブラシをビットマップにするなどをすれば
もっと凝った、複雑なグラフィックスアプローチが可能になるでしょう

しかし、ここである問題が浮上します
実は、これらの様にパスを描画すると、パスは無効になるのです
それでは、輪郭と塗りつぶしを同時に行いたい場合はどのようにするのでしょうか?

そのような場合には StrokeAndFillPath() 関数を用います
この関数を用いれば、カレントペン、カレントブラシで輪郭と内部を描画します

BOOL StrokeAndFillPath(HDC hdc);

hdc にはデバイスコンテキストのハンドルを指定します
関数が成功すれば 0 以外、失敗すれば 0 になります
上のプログラムの FillPath() 関数を StrokeAndFillPath() に変えてコンパイルしてみてください
以下にように、輪郭と内部が現在のペンとブラシで描画されていることが確認できます



しかし、パスはいずれにしても描画してしまえば解除されてしまうので
保存したい場合は PathToRegion() 関数を用います
この関数は、パスをリージョンに変換します

HRGN PathToRegion(HDC hdc);

hdc にはデバイスコンテキストのハンドルを指定します
関数が成功するとリージョンのハンドルが、失敗すると 0 が返ります
この時も、パスは解除されます

また、パスをクリッピング領域に設定することも可能です
この時、現在のクリッピング領域と論理演算で範囲を決定できます
これを行うには SelectClipPath() 関数を用います

BOOL SelectClipPath(HDC hdc , int iMode);

hdc にはデバイスコンテキストのハンドル
iMode には、モードを定数で指定します
ここに指定する定数は、リージョンで使った RGN_* 定数です
成功すれば 0 以外、失敗すれば 0 が返ります
#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;
	HFONT hFont;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);

		BeginPath(hdc);

		SetBkMode(hdc , TRANSPARENT);
		hFont = CreateFont(
			80 , 0 , 0 , 0 , FW_BOLD , FALSE , FALSE , FALSE ,
			ANSI_CHARSET , OUT_DEFAULT_PRECIS ,
			CLIP_DEFAULT_PRECIS , DEFAULT_QUALITY , 
			0 , NULL
		);
		SelectObject(hdc , hFont);
		TextOut(hdc , 0 , 0 , TITLE , lstrlen(TITLE));

		EndPath(hdc);

		SelectObject(hdc , GetStockObject(SYSTEM_FONT));
		DeleteObject(hFont);
		SelectClipPath(hdc , RGN_COPY);

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


このプログラムは、クリッピング領域にコマンドラインから指定した DIB を描画します
これまでの DIB プログラム同様に、エラーチェックはしていないので
Windows フルカラー DIB 以外のビットマップファイルは指定しないでください

上の図を見れば、パスにしたがってクリッピングされていることが確認できます
背景のビットマップには、画像ソフトのフィルタで作成した雲模様を使いました


BeginPath()

BOOL BeginPath(HDC hdc);

パスをオープンします

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

戻り値 - 成功すれば 0 以外、失敗すれば 0

EndPath()

BOOL EndPath(HDC hdc);

パスを閉じ、そのパスをデバイスコンテキストに設定します

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

戻り値 - 成功すれば 0 以外、失敗すれば 0

StrokePath()

BOOL StrokePath(HDC hdc);

パスの輪郭を現在のペンで描画します

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

戻り値 - 成功すれば 0 以外、失敗すれば 0

FillPath()

BOOL FillPath(HDC hdc);

パスの内部を現在のブラシと多角形モードで塗りつぶします
パスに開いた図形が含まれている場合、自動的に図形は閉じられます

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

戻り値 - 成功すれば 0 以外、失敗すれば 0

StrokeAndFillPath()

BOOL StrokeAndFillPath(HDC hdc);

パスの輪郭を現在のペンで描き
内部を現在のブラシと多角形モードで塗りつぶします
パスに開いた図形が含まれている場合、自動的に図形は閉じられます

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

戻り値 - 成功すれば 0 以外、失敗すれば 0

PathToRegion()

HRGN StrokePath(HDC hdc);

パスをリージョンに変換し、リージョンを返します
その後、選択されているパスは解除されます

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

戻り値 - リージョンのハンドル、失敗すれば 0

SelectClipPath()

BOOL SelectClipPath(HDC hdc , int iMode);

パスを現在のクリッピング領域と組み合わせます

hdc - デバイスコンテキストのハンドルを指定します
iMode - 組み合わせモードを指定します

戻り値 - 成功すれば 0 以外、失敗すれば 0

iMode には以下の定数のいずれかを指定することができます

定数解説
RGN_AND 現在のクリッピング領域と
現在のパスの両方に含まれる領域を
新しいクリッピング領域にします
RGN_COPY 現在のパスを、新しいクリッピング領域にします
RGN_DIFF 現在のクリッピング領域から
現在のパスを除いた領域を
新しいクリッピング領域にします
RGN_OR 現在のクリッピング領域と
現在のパスを組み合わせた領域を
新しいクリッピング領域にします
RGN_XOR 現在のクリッピング領域と
現在のパスを組み合わせた領域から
重なる部分を除いた領域を、新しいクリッピング領域にします




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