概要

GDI(Graphics Device Interface)も数々のグラフィックインターフェースと同様にウィンドウとビューポートという概念があります。
ウィンドウをどのようにビューポートに反映させる方法をマッピングモードと言い変更すると、図形を描画する際に原点位置の変更により移動または拡大縮小ができます。
また、通常Y座標は上から下に向かって数値が大きくなりますが、数学の様に下から上に向かって数値が大きくすることもできます。
なお、ここで言うウィンドウはウィンドウで開くウィンドウではありません。GDI APIで指定する座標の値のことです。
描画関数で指定する座標系を論理座標(ウィンドウ)、デバイスに描画される座標系を物理座標(ビューポート)と呼びます。 SetMapMode APIで論理座標を物理座標に変換するマッピング方法、SetWindowExtExが論理座標のスケール、SetViewportExtExが物理座標のスケールを指定します。スケールにマイナスを指定すると座標の方向が逆になります。
物理座標のスケールが2で論理座標のスケールが1の場合、論理座標に描画される値は物理座標では倍に変換されます。
デバイスコンテキストを介して描画するため、デバイスの解像度を意識していれば同一のAPIにより異なるデバイスにも描画することもできます。
本プログラムでは、以下の様に設定しています。マッピングモードを適用しない場合とした場合を示すため方眼紙上の図形を描画を表示します。
SetMapMode(hdc,MM_ANISOTROPIC);
SetWindowExtEx(hdc,1000,1000,NULL);
SetViewportExtEx(hdc,500,500,NULL);
SetViewportOrgEx(hdc,50,30,NULL);
大きな方眼紙がマッピング前、小さな方眼紙がマッピング後となります。マッピングによりウィンドウの原点がビューポートの50,30に移動し、大きさは半分となっています。

座標は半分になっていますが、塗りつぶしや文字の大きさ線の太さは変わっていないことがわかるかと思います。
本プログラムではビューポートの原点を移動させていますので移動量はスケーリングの影響を受けません。
またビューポートでYのスケーリングに負の値を指定し原点を移動させた場合、移動させた原点から上に向かってY座標が増えます。
SetWindowOrgEx APIを用いて原点を移動させるとウィンドウ上の座標が移動するのでスケーリングの影響を受けます。
またビューポート上で右に動かすためにはウィンドウ上の原点は負の値を指定し右に動かす量が大きいほど負の値を大きくする必要があります。すなわちビューポートで動かす場合に対して正負を逆にする必要があります。
各マッピング関係の関数によりウィンドウ座標の値がビューポート上でどの座標に変換されるかを示した式が以下の通りです。
SetWindowExtEx(hdc,Wxs,Wys,NULL);
SetViewportExtEx(hdc,Vxs,Vys,NULL);
SetWindowOrgEx(hdc,Wxo,Wyo,NULL);
SetViewportOrgEx(hdc,Vxo,Vyo,NULL);
ウィンドウ座標上の点(Wx,Wy)
ビューポート座標上の点(Vx,Vy)


SetMapMode

マッピングモードを指定します。
マッピングモードには以下のものがあります。
インチやポイントで指定できるマッピングモードを正常に使用するためにはデバイスのDPI等が正常に指定されている必要があります。
例えばディスプレイはWindows上では標準値が96DPIとなっています。
1ポイントは1/72インチです。1/20ポイントは1/(72*20)=1/1440インチとなります。1インチは25.4mmです。
ディフォルトのマッピングモードはMM_TEXTです。
SetMapMode( デバイスコンテキスト , マッピングモード)
マッピングモード値説明座標方向
MM_ANISOTROPICSetWindowExtExとSetViewportExtEx関数により尺度等をスケーリングすることができます。 0 x y SVGの代替画像
MM_HIENGLISH描画する座標値を0.001インチを1として描画します。例えばLineToExで100の長さの直線を描画するとデバイスには0.1インチ(2.54mm)の長さの線となります。 0 x y SVGの代替画像
MM_HIMETRIC描画する座標値を0.01mmを1として描画します。例えばLineToExで100の長さの直線を描画するとデバイスには1mmの長さの線となります。 0 x y SVGの代替画像
MM_ISOTROPIC 0 x y SVGの代替画像
MM_LOENGLISH描画する座標値を0.01インチを1として描画します。例えばLineToExで100の長さの直線を描画するとデバイスには1インチ(25.4mm)の長さの線となります。 0 x y SVGの代替画像
MM_LOMETRIC描画する座標値を0.1mmを1として描画します。例えばLineToExで100の長さの直線を描画するとデバイスには10mmの長さの線となります。 0 x y SVGの代替画像
MM_TEXT描画する座標値をそのままデイバスのピクセルとして描画します。例えばLineToExで100の長さの直線を描画するとデバイスには100ピクセルの長さの線となります。 0 x y SVGの代替画像
MM_TWIPS描画する座標値を1/20ポイントとして描画します。例えばLineToExで100の長さの直線を描画するとデバイスには5ポイントの長さの線となります。 0 x y SVGの代替画像

SetWindowExtEx

論理座標系のスケールを指定します。ディフォルトではx座標・y座標ともに1が指定されています。
SetSetWindowExtEx( デバイスコンテキスト , x方向スケーリング,y方向スケーリング,以前の論理座標系のスケーリングを格納するポインタ)
第4引数にNULLを指定することができます。

SetViewportExtEx

物理座標系のスケールを指定します。ディフォルトではx座標・y座標ともに1が指定されています。
SetViewportExtEx( デバイスコンテキスト , x方向スケーリング,y方向スケーリング,以前の物理座標系のスケーリングを格納するポインタ)
第4引数にNULLを指定することができます。

SetWindowOrgEx

論理座標系の原点を指定します。
SetWindowOrgEx( デバイスコンテキスト , 論理座標xの原点,論理座標yの原点,以前の論理座標系の原点を格納するポインタ)
例えばx座標の原点に正の値を指定すると描画を右側にずらすことができます。
第4引数にNULLを指定することができます。

SetViewportOrgEx

論理座標系の原点を指定します。
SetViewportOrgEx( デバイスコンテキスト , 論理座標xの原点,論理座標yの原点,以前の論理座標系の原点を格納するポインタ)
例えばx座標の原点に正の値を指定すると描画を右側にずらすことができます。
第4引数にNULLを指定することができます。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 7 EnterPrise Service Pack 1 64bit
Windows 10 Home 32/64bit

プログラムソースの概要

mapmode.cpp

_tWinMain

Windowsから最初に_tWinMain関数が呼び出されます。 ウィンドウを作成する場合は、RegisterClass APIによりウィンドウクラスを定義してからCreateWindow APIを呼び出しウィンドウを作成します。 Windowsは入力等のイベントが発生するとアプリケーションにメッセージを送付します。 メッセージはキューに保管されます。アプリケーションはメッセージを取り出し、該当ウィンドウにメッセージを配信します。 メッセージの取り出しから配信までループで処理を行いウィンドウから終了メッセージが届くと、ループを抜けるように記述します。 これらの一連の処理は、通常にCreateWindow APIの後に記述しこれをメッセージループと呼んでいます。 詳細は以下を参照してください。
メッセージループについて

WndProc

RegisterClass APIにより登録することによりWindowsから呼び出されます。
第2引数にメッセージの種類が格納されていますので、switchステートメントによりメッセージごとの処理を振り分けます。
自分で処理しないメッセージはDefWindowProc APIに渡せばWindowsが標準的な処理を行ってくれます。

case WM_DESTROY:

ウィンドウが閉じるときに呼び出されます。
PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。

case WM_PAINT:

ウィンドウを再描画する必要があるときに呼び出されます。
他のウィンドウに隠れ再びフォアグラウンドになった場合などウィンドウの再描画はWindowsが面倒を見ないのでプログラマの仕事となっています。
BeginPaint APIを呼び出して、描画に必要なデバイスコンテキストのハンドルを取得します。
BeginPaint APIの第2引数のポインタにはPAINTSTRUCT構造体のポインタを渡します。
BeginPaint API終了後、PAINTSTRUCT構造体には再描画が必要な領域の座標等の値が格納されています。
CreatePen APIにより赤と青のペンをペンを作成します。
CreateHatchBrush APIによりクロスハッチのブラシを作成します。
最初にmesh_draw関数を呼び出しマッピングモード適用前の方眼紙上の図形を表示します。
SetMapMode APIによりマッピングモードをMM_ANISOTROPICに設定します。
SetWindowExtEx APIによりウィンドウサイズx,yのサイズを1000、 SetViewportExtEx APIによりビューポートサイズx,yのサイズを500、 SetViewportOrgEx APIによりビューポート原点を50,30に設定します。
変更されたマッピングでmesh_draw関数を呼び出しマッピングモード適用後の方眼紙上の図形を表示します。
DeleteObject APIにより作成したペン及びブラシのオブジェクトを解放します。
再描画が終了したことをWindowsに知らせるためにEndPaint APIを使用してます。このAPIを呼び出さないと何度もWM_PAINTメッセージが発生し暴走します。

mesh_draw

MoveToExとLineTo APIによりウィンドウ上の大きさが200*200、方眼紙の間隔が20の方眼紙を描画します。
TextOut APIにより方眼紙のメモリ値を表示します。
Ellipse APIにより円、 Rectangle APIにより長方形を描画します。 TextOut APIによりウィンドウに文字を描画します。
文字の種類や大きさ等はディフォルトの値が使われます。
変更したい場合はCreateFont APIで新たにフォントを作成しSelectObjectでフォントを選択し、フォントが不要になったら、DeleteObject APIでフォントを削除します。

ソースコード

mapmode.cpp

//	マッピングモードのテスト

#include <windows.h>
#include <tchar.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//	200*200の方眼紙を描画
void mesh_draw(HDC hdc,HPEN hPen1,HPEN hPen2);

TCHAR szClassNme[] = TEXT("MapMode");

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,TCHAR* lpszCmdLine, int nCmdShow){
	HWND hWnd;
	MSG lpMsg;
	WNDCLASS myProg;

	if (!hPreInst) {
		myProg.style =CS_HREDRAW | CS_VREDRAW;
		myProg.lpfnWndProc =WndProc;
		myProg.cbClsExtra =0;
		myProg.cbWndExtra =0;
		myProg.hInstance =hInstance;
		myProg.hIcon =NULL;
		myProg.hCursor =LoadCursor(NULL, IDC_ARROW);
		myProg.hbrBackground =(HBRUSH)GetStockObject(WHITE_BRUSH);
		myProg.lpszMenuName =NULL;
		myProg.lpszClassName =szClassNme;
		if (!RegisterClass(&myProg))
			return FALSE;
	}
	hWnd = CreateWindow(szClassNme,
	szClassNme,
	WS_OVERLAPPEDWINDOW,
	CW_USEDEFAULT,
	CW_USEDEFAULT,
	CW_USEDEFAULT,
	CW_USEDEFAULT,
	NULL,
	NULL,
	hInstance,
	NULL);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	while (GetMessage(&lpMsg, NULL, 0, 0)) {
		TranslateMessage(&lpMsg);
		DispatchMessage(&lpMsg);
	}
	return int(lpMsg.wParam);
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
	HDC hdc;
	PAINTSTRUCT ps;

	switch (msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		case WM_PAINT:{
			hdc = BeginPaint(hWnd, &ps);
			HPEN hPen1,hPen2;
			hPen1 = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
			hPen2 = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
			HBRUSH hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(0, 255, 0));
			SelectObject(hdc, hBrush);
			//	マッピングモード適用前の図形を描画
			mesh_draw(hdc,hPen1,hPen2);
			//	ケーリング・原点を設定
			SetMapMode(hdc,MM_ANISOTROPIC);
			SetWindowExtEx(hdc,1000,1000,NULL);
			SetViewportExtEx(hdc,500,500,NULL);
			SetViewportOrgEx(hdc,50,30,NULL);
			//	マッピングモード適用後の図形を描画
			mesh_draw(hdc,hPen1,hPen2);
			DeleteObject(hPen1);
			DeleteObject(hPen2);
			DeleteObject(hBrush);
			EndPaint(hWnd, &ps);
			break;}
		default:
			return(DefWindowProc(hWnd, msg, wParam, lParam));
	}
	return (0L);
}

//	200*200の方眼紙を描画

void mesh_draw(HDC hdc,HPEN hPen1,HPEN hPen2){
	int x,y;
	TCHAR buf[8];
	for(x=0;x<=200;){
		if(( x % 100)==0)
			SelectObject(hdc, hPen2);
		else
			SelectObject(hdc, hPen1);
		MoveToEx(hdc,x,0,NULL);
		LineTo(hdc,x,200);
		x+=20;
	}
	for(y=0;y<=200;){
		if(( y % 100)==0)
			SelectObject(hdc, hPen2);
		else
			SelectObject(hdc, hPen1);
		MoveToEx(hdc,0,y,NULL);
		LineTo(hdc,200,y);
		y+=20;
	}
	SelectObject(hdc, hPen2);
	for(x=100;x<=200;){
		TCHAR buf[16];
		wsprintf(buf,_TEXT("%d"),x);
		TextOut(hdc , x-10 , 203 , buf , (int)_tcslen(buf));
		x+=100;
	}
	for(y=100;y<200;){
		wsprintf(buf,_TEXT("%d"),y);
		TextOut(hdc , 202 , y-10 , buf , (int)_tcslen(buf));
		y+=100;
	}
	Ellipse(hdc,0,150,50,200);
	Rectangle(hdc,120,160,160,180);
}

ソースコードと実行ファイルのダウンロード