マッピングモード


マップモードの変更

多くの GDI ファンクションなどでは、描画ポイントを示すために
X 座標と Y 座標を表す数値を指定します

通常、この座標は対象矩形の最も左上を (0 , 0) とする座標でした
そして、これら座標を表す各単位をピクセルと呼ぶこともご存知のとおりです
しかし、Windows アプリケーションはこれらの設定を変更することができます

例えば、数学は中央を原点として Y 軸は上に行くほど + されるというのが常識です
下に行くほど Y 座標の値が増え、原点は左上のマッピングモードは
数学関係のアプリケーション開発には、適しているとは言えません

この他にも、プリントアウトする画像では、ピクセル単位よりもメートル単位の方がよいなど
理由は様々に考えられますが、このようなことからマッピングモードを変更します
マッピングモードの変更には SetMapMode() 関数を使います

int SetMapMode(HDC hdc , int fnMapMode);

hdc にはデバイスコンテキストのハンドルを
fnMapMode には、マッピングモードを表す定数を指定します
関数が成功すれば以前のマッピングモードが、失敗すれば 0 が返ります

マッピングモードを表す定数は、下のリファレンスを参照してください
この場では、0.1 ミリ単位の MM_LOMETRIC を用いることにしましょう
これは、X 座標は右に、Y 座標は上に向かって増えていきます
#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;
	TEXTMETRIC tm;

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

		GetTextMetrics(hdc , &tm);
		TextOut(hdc , 100 , tm.tmHeight , TITLE , lstrlen(TITLE));

		SetMapMode(hdc , MM_LOMETRIC);
		TextOut(hdc , 100 , 0 , TITLE , lstrlen(TITLE));

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


図の上の文字列は、 MM_LOMETRIC で指定したものです
0.1 * 100 で X 座標 10 ミリの地点を原点として文字列が描画されます
下の文字列は、従来の MM_TEXT モードの 100 ピクセルの地点から描画されています


論理座標と物理座標

コンピュータの世界は、ずいぶんと「論理」と「物理」という言葉が好きですが
マッピングモードでも、この言葉が使われています

マッピングモードでは、時に論理座標をウィンドウと呼び
物理座標をビューポートと呼びます
これは、従来のウィンドウ(HWND)という用語とは関係がないので注意してください

ビューポートの単位は常にピクセルですが、ウィンドウの座標はマッピングモードで異なります
GDI はウィンドウの座標(すなわち論理座標)を受けとって座標を処理しているのです

ところが、この論理座標と物理座標を使い分けていると、混乱がおこるかもしれません
できることならば、これらの座標を変換する方法があればいいと思うでしょう
実は、そのための関数を Windows は用意しています

論理座標を物理座標に変換するならば LPtoDP() 関数を
物理座標を論理座標に変換するならば DPtoLP() 関数を使います

BOOL DPtoLP(HDC hdc , LPPOINT lpPoints , int nCount);
BOOL LPtoDP(HDC hdc , LPPOINT lpPoints , int nCount);

hdc にはデバイスコンテキストのハンドルを
lpPoint には変換する対象が格納されている POINT 構造体配列へのポインタを
nCount には、lpPoint の配列数を指定します

関数が成功すれば、lpPoint の点が変換され 0 以外の数値が返ります
失敗した場合は 0 が返ります
論理座標が 32 ビットを超えるときや、物理座標が 27 ビットを超えるときは
オーバーフローによって、関数は失敗します
#include <windows.h>

#define TITLE TEXT("Kitty on your lap")
#define MES TEXT("論理座標 X = %dmm , Y = %dmm\n物理座標 X = %dpx , Y = %dpx")

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	POINT poLP , poDP;
	RECT rect;
	TCHAR str[1024];

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

		GetCursorPos(&poDP);
		poLP = poDP;
		
		GetClientRect(hWnd , &rect);
		SetMapMode(hdc , MM_LOMETRIC);
		DPtoLP(hdc , &poLP , 1);
		SetMapMode(hdc , MM_TEXT);

		wsprintf(str , MES , poLP.x / 10 , poLP.y / 10 , poDP.x , poDP.y);
		DrawText(hdc , str , -1 , &rect , DT_LEFT);
		
		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;
}


このプログラムは、ウィンドウ内のカーソルの位置を
ミリメートル単位と、ピクセル単位で表示するというものです
WM_PAINT の処理で、ミリメートル単位に座標を変更するために
極一時的に、マッピングモードを変更しています


原点の変更

ウィンドウや、ビューポートの原点を変更することも可能です
数学関数の表を描画するプログラムなどでは、原点は左上よりも
むしろ、描画対象矩形の中央が (0 , 0) であることのほうが都合が良いはずです

これ以外にも様々な理由が考えられますが
やはり、原点の変更は柔軟な位置指定を可能とする重要な要素です

ビューポートの原点を変更するには SetViewportOrgEx() 関数を
ウィンドウの原点を変更するには SetWindowOrgEx() 関数を使います

BOOL SetViewportOrgEx(HDC hdc , int X , int Y , LPPOINT lpPoint);
BOOL SetWindowOrgEx(HDC hdc , int X , int Y , LPPOINT lpPoint);

それぞれ、hdc にはデバイスコンテキストのハンドルを
X には新しい原点の X 座標、Y には新しい原点の Y 座標を
lpPoint には以前の原点を格納する POINT 構造体へのポインタを指定します
lpPoint は必要なければ NULL でもかまいません
関数が成功すると 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;

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

		SetViewportOrgEx(hdc , 100 , 50 , NULL);
		TextOut(hdc , 0 , 0 , TITLE , lstrlen(TITLE));

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


このプログラムは、ビューポートの原点を変更しています
ビューポートの原点が (100 , 50) の地点に変更した状態で
TextOut() では (0 , 0) 座標に文字を描画しています

また、現在の原点を取得する関数も存在します
ビューポートの原点の座標を得るには GetViewportOrgEx() 関数を
ウィンドウの原点の座標を得るには GetWindowOrgEx() 関数を使用します

BOOL GetViewportOrgEx(HDC hdc , LPPOINT lpPoint);
BOOL GetWindowOrgEx(HDC hdc , LPPOINT lpPoint);

hdc にはデバイスコンテキストのハンドルを
lpPoint には現在の原点の座標を受け取る POINT 構造体へのポインタを指定します
成功すると 0 以外、失敗すると 0 が返ります


SetMapMode()

int SetMapMode(HDC hdc , int fnMapMode);

マッピングモードを変更します

hdc - デバイスコンテキストのハンドルを指定します
fnMapMode - マッピングモードを表す定数を指定します

戻り値 - 成功すれば以前のマッピングモード。失敗すれば 0

fnMapMode には以下のいずれかの定数を指定します

定数解説
MM_ANISOTROPIC 論理単位は、任意にスケーリングされた軸上の任意の単位にマップされます
単位、向き、スケーリングを指定するには
SetWindowExtEx() と SetViewportExtEx() を使います
MM_HIENGLISH 各論理単位は、0.001 インチにマップされます
x の値は右に、y の値は上に向かって増加します
MM_HIMETRIC 各論理単位は、0.01 ミリメートルにマップされます
x の値は右に、y の値は上に向かって増加します
MM_ISOTROPIC 論理単位は、等しくスケーリングされた軸上の任意の単位にマップされます
すなわち、X 軸方向の 1 単位は、Y 軸方向の 1 単位と同じになります
単位と向きを指定するには、SetWindowExtEx() と SetViewportExtEx () を使います
GDI は、必要に応じて x 単位と y 単位のサイズが同一になるように調整を行います
MM_LOENGLISH 各論理単位は、0.01 インチにマップされます
x の値は右に、y の値は上に向かって増加します
MM_LOMETRIC 各論理単位は、0.1 ミリにマップされます
x の値は右に、y の値は上に向かって増加します
MM_TEXT 各論理単位は、1 デバイスピクセルにマップされます
x の値は右に、y の値は下に向かって増加します
MM_TWIPS 各論理単位は、ポイント数の 20 分の 1 にマップされます
(1/1440 インチ、“twip”とも呼ばれます)
x の値は右に、y の値は上に向かって増加します

LPtoDP()

BOOL LPtoDP(HDC hdc , LPPOINT lpPoints , int nCount);

論理座標を物理座標に変換します

hdc - デバイスコンテキストのハンドルを指定します
lpPoint - 変換対象の POINT 構造体配列のポインタを指定します
nCount - lpPoint の配列数を指定します

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

DPtoLP()

BOOL DPtoLP(HDC hdc , LPPOINT lpPoints , int nCount);

物理座標を論理座標に変換します

hdc - デバイスコンテキストのハンドルを指定します
lpPoint - 変換対象の POINT 構造体配列のポインタを指定します
nCount - lpPoint の配列数を指定します

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

SetViewportOrgEx()

BOOL SetViewportOrgEx(HDC hdc , int X , int Y , LPPOINT lpPoint);

物理座標の原点を変更します

hdc - デバイスコンテキストのハンドルを指定します
X - 新しい原点の X 座標を指定します
Y - 新しい原点の Y 座標を指定します
lpPoint - 以前の原点を格納する POINT 構造体へのポインタを指定します

戻り値 - 関数が成功すると 0 以外、失敗すると 0 が返ります

SetWindowOrgEx()

BOOL SetWindowOrgEx(HDC hdc , int X , int Y , LPPOINT lpPoint);

論理座標の原点を変更します

hdc - デバイスコンテキストのハンドルを指定します
X - 新しい原点の X 座標を指定します
Y - 新しい原点の Y 座標を指定します
lpPoint - 以前の原点を格納する POINT 構造体へのポインタを指定します

戻り値 - 関数が成功すると 0 以外、失敗すると 0 が返ります

GetViewportOrgEx()

BOOL GetViewportOrgEx(HDC hdc , LPPOINT lpPoint);

現在の物理座標の原点を取得します

hdc - デバイスコンテキストのハンドルを指定します
lpPoint - 原点の座標を受け取る POINT 構造体へのポインタを指定します

戻り値 - 成功すると 0 以外、失敗すると 0

GetWindowOrgEx()

BOOL GetWindowOrgEx(HDC hdc , LPPOINT lpPoint);

現在の論理座標の原点を取得します

hdc - デバイスコンテキストのハンドルを指定します
lpPoint - 原点の座標を受け取る POINT 構造体へのポインタを指定します

戻り値 - 成功すると 0 以外、失敗すると 0



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