EternalWindows
GDI / ブラシ

GDIによる描画に影響を及ぼす情報は、 前節で述べたデバイスコンテキストの属性と、今回取り上げるGDIオブジェクトの2つです。 GDIオブジェクトはそれ自身の属性を管理し、 デバイスコンテキストに選択できるオブジェクトのことです。 たとえば、テキストの描画には色などの属性の他に、スタイル(斜体など)や太さなども重要であるといえますが、 こうした属性はデバイスコンテキストの属性ではなく、フォントと呼ばれるGDIオブジェクトの属性になっています。 GDIオブジェクトをデバイスコンテキストに選択した場合は、 描画の際にその選択されたGDIオブジェクトの属性が使用されることになります。

次に、GDIオブジェクトの一部を示します。

名前 識別する型 管理している属性
ブラシ HBRUSH 図形の内部の色や塗りつぶし方
ペン HPEN 直線や曲線の太さやスタイル(破線や点線)
リージョン HRGN 図形から作成された領域、クリッピングリージョン
フォント HFONT 文字の幅や太さ、使用する文字セット
ビットマップ HBITMAP 読み込まれたビットマップ、デバイスと互換性のあるビットマップ

このようにGDIオブジェクトは複数存在しますが、 どのGDIオブジェクトにも共通で代表的な5つの事柄があります。

1. GDIオブジェクトは、デバイスコンテキストから独立している。
2. GDIオブジェクトは、それ自身の属性を管理している。
3. GDIオブジェクトは、作成と削除の概念がある。
4. GDIオブジェクトは、デバイスコンテキストに選択することができる。
5. 選択したオブジェクトは、使い終えたら元のオブジェクトに置き換える。

それでは、実際にGDIオブジェクトを扱って上記の事柄を証明していきましょう。 本節で扱うのはブラシというGDIオブジェクトです。 ブラシは図形の内部を塗りつぶすための属性を管理し、CreateSolidBrushで作成することができます。

HBRUSH CreateSolidBrush(
  COLORREF crColor
);

crColorは、ブラシの色を指定します。 戻り値のHBRUSH型はブラシを識別するためのハンドルです。 つまり、内部的に作成されたブラシというGDIオブジェクトを、プログラムはHBRUSHで表します。

ブラシに限らず、作成したGDIオブジェクトはDeleteObjectで削除します。

BOOL DeleteObject(
  HGDIOBJ hObject
);

hObjectは、削除したいGDIオブジェクトを指定します。 これにより、GDIオブジェクトために使用されていたメモリは開放されます。 HGDIOBJという型は「この引数にはGDIオブジェクトの型を指定せよ」 ということを示すだけで、実際にこの型を利用することは少ないと思われます。

GDIオブジェクトはデバイスコンテキストから独立しているため、 GDIオブジェクトを作成する関数はデバイスコンテキストを必要としません。 独立しているからこそ、そのGDIオブジェクトためのメモリを確保したわけです。 そのため、GDIオブジェクトをデバイスコンテキストに関連付けるには、 GDIオブジェクトをデバイスコンテキストに選択するという作業が必要になります。

HGDIOBJ SelectObject(
  HDC hdc,
  HGDIOBJ hgdiobj
);

hdcは、デバイスコンテキストのハンドルを指定します。 hgdiobjは、デバイスコンテキストに選択するGDIオブジェクトを指定します。

SelectObjectを呼び出すと、デバイスコンテキストの属性は変更されます。 たとえば、ブラシを選択したならば描画関数を呼び出した際には、 そのブラシの属性が反映されることになります。 デバイスコンテキストの[ブラシの色]という属性を変更したのではなく、 ブラシという属性を変更したということに注意してください。 色事体はブラシの属性なので、HBRUSHが管理しています。

今回のプログラムは、デバイスコンテキストにブラシを選択してから、Rectangleを呼び出します。

#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 HBRUSH hbr = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hbr = CreateSolidBrush(RGB(0, 255, 0));
		return hbr != NULL ? 0 : -1;

	case WM_PAINT: {
		HDC         hdc;
		HBRUSH      hbrPrev;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);
		
		hbrPrev = (HBRUSH)SelectObject(hdc, hbr);

		Rectangle(hdc, 10, 10, 60, 60);

		SelectObject(hdc, hbrPrev);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (hbr != NULL)
			DeleteObject(hbr);

		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

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

ブラシは図形の内部を塗りつぶすGDIオブジェクトです。 内部というのはRectangleで例えると枠の中です。 ブラシを作成し、Rectangleのような図形を描画する関数を呼び出す前に選択しておけば、 図形の内部はブラシの属性で塗りつぶされます。 ブラシの作成はWM_CREATEで行っています。

case WM_CREATE:
	hbr = CreateSolidBrush(RGB(0, 255, 0));
	return hbr != NULL ? 0 : -1;

ここでブラシを作成せずに描画が必要になったとき、つまりWM_PAINTが送られる度に 作成する方法もありますが、同じブラシを使い回せば効率がよいのは明らかですし、 作成や読み込みなどの処理はWM_CREATEで行うのが定石です。 GDIオブジェクトを作成する関数は処理に失敗するとNULLを返します。 ブラシの作成を失敗することはまず考えられませんが、 NULLのときは-1を返すようにしています。 WM_CREATEで-1を返すとWM_DESTROYが処理されることになります。

case WM_DESTROY:
	if (hbr != NULL)
		DeleteObject(hbr);

	PostQuitMessage(0);

	return 0;

hbrがNULLであるときは、ブラシを作成できなかったことを意味するので、 そのときはDeleteObjectを呼び出すべきではありません。

WM_PAINTではブラシをデバイスコンテキストに選択しています。

hbrPrev = (HBRUSH)SelectObject(hdc, hbr);

Rectangle(hdc, 10, 10, 60, 60);

SelectObject(hdc, hbrPrev);

1回目のSelectObjectで元のブラシを保存しているのが重要です。 元のブラシというのはデバイスコンテキストに最初から選択されている デフォルトのブラシのことです。 GDIオブジェクトは使い終えたら元に戻すべきであるという決まりがあるので、 Rectangleの呼び出しの後で、保存したブラシを選択します。 元に戻す作業は、デバイスコンテキストの開放の前に行うようにしてください。

今回はGDIオブジェクトとしてブラシを選びましたが、 他のGDIオブジェクトを扱う場合でもその手順は次のような流れになります。

操作 関数
作成 CreateXXX
選択 SelectObject
属性の取得 GetObject
削除 DeleteObject

まず、初期化関数(これはGDIオブジェクト固有)を呼び出し、 それが必要になったときにSelectObjectで選択、 そしてプログラムの終了時にDeleteObjectで削除となります。 次節ではペンについて取り上げますが、 今回のプログラムと多くの面で似ていることが分かります。

CreateHatchBrushとCreateBrushIndirect

CreateSolidBrushで作成したブラシは、図形の内部を単純に塗りつぶすというものでしたが、 一定方向に塗りつぶすハッチブラシというブラシもあります。 ハッチブラシを作成するには、CreateHatchBrushを呼び出します。

hbr = CreateHatchBrush(HS_DIAGCROSS, RGB(0, 255, 0));

第1引数には、どのように塗りつぶすかを示す定数を指定します。 HS_DIAGCROSSを指定した場合は、斜め45度で塗りつぶされることになります。 第2引数は、塗りつぶしに使用する色を指定します。

ブラシの作成は、CreateBrushIndirectで行うこともできます。

LOGBRUSH lb;

lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 255, 0);
lb.lbHatch = 0;

hbr = CreateBrushIndirect(&lb);

CreateBrushIndirectを呼び出すには、LOGBRUSH構造体を初期化することになります。 lblbStyleは、どのようなブラシを作成するかを示す定数を指定します。 ソリッドブラシを作成する場合はBS_SOLIDを指定し、 ハッチブラシを作成する場合はBS_HATCHEDを指定します。 lbColorはブラシの色を指定し、lbHatchはハッチブラシに関する定数を指定します。 lbStyleにBS_SOLIDを指定している場合は0で構いませんが、 BS_HATCHEDを指定した場合はHS_DIAGCROSSなどを指定します。



戻る