Win32應(yīng)用程序設(shè)計(jì)接口(API)使用四種坐標(biāo)空間:世界坐標(biāo)系空間、頁面空間、設(shè)備空間、和物理設(shè)備空間。應(yīng)用程序運(yùn)用世界坐標(biāo)系空間對(duì)圖形輸出進(jìn)行旋轉(zhuǎn)、斜切或者反射(一般坐標(biāo)系完不成這些特殊的功能)。
Win32 API把世界坐標(biāo)系空間和頁面空間稱為邏輯空間;最后一種坐標(biāo)空間(即物理設(shè)備空間)通常指應(yīng)用程序窗口的客戶區(qū);但是它也包括整個(gè)桌面、完整的窗口(包括框架、標(biāo)題欄和菜單欄)或打印機(jī)的一頁或繪圖儀的一頁紙。
如果該應(yīng)用程序調(diào)用了SetWorldTransform函數(shù),那么映射就從應(yīng)用程序的世界坐標(biāo)系空間開始;否則,映射在頁面空間中進(jìn)行。
下面有一個(gè)從MSDN中拷貝出來的例子如下:
enum TransformStyle { SCALE, TRANSLATE, ROTATE, SHEAR, REFLECT, NORMAL};
void CXXXXView::TransformAndDraw(int iTransform, HWND hWnd)
{
HDC hDC;
XFORM xForm;
RECT rect;
// Retrieve a DC handle for the application's window.
hDC = ::GetDC(hWnd);
// Set the mapping mode to LOENGLISH. This moves the
// client area origin from the upper left corner of the
// window to the lower left corner (this also reorients
// the y-axis so that drawing operations occur in a true
// Cartesian space). It guarantees portability so that
// the object drawn retains its dimensions on any display.
SetGraphicsMode(hDC, GM_ADVANCED);
SetMapMode(hDC, MM_LOENGLISH);
// Set the appropriate world transformation (based on the
// user's menu selection).
switch (iTransform)
{
case SCALE: // Scale to 1/2 of the original size.
xForm.eM11 = (FLOAT) 0.5;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 0.5;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case TRANSLATE: // Translate right by 3/4 inch.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 1.0;
xForm.eDx = (FLOAT) 75.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case ROTATE: // Rotate 30 degrees counterclockwise.
xForm.eM11 = (FLOAT) 0.8660;
xForm.eM12 = (FLOAT) 0.5000;
xForm.eM21 = (FLOAT) -0.5000;
xForm.eM22 = (FLOAT) 0.8660;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case SHEAR: // Shear along the x-axis with a
// proportionality constant of 1.0.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 1.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 1.0;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case REFLECT: // Reflect about a horizontal axis.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) -1.0;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case NORMAL: // Set the unity transformation.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 1.0;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
default:
break;
}
// Find the midpoint of the client area.
::GetClientRect(hWnd, (LPRECT) &rect);
DPtoLP(hDC, (LPPOINT) &rect, 2);
// Select a hollow brush.
SelectObject(hDC, GetStockObject(HOLLOW_BRUSH));
// Draw the exterior circle.
Ellipse(hDC, (rect.right / 2 - 100), (rect.bottom / 2 + 100),
(rect.right / 2 + 100), (rect.bottom / 2 - 100));
// Draw the interior circle.
Ellipse(hDC, (rect.right / 2 -94), (rect.bottom / 2 + 94),
(rect.right / 2 + 94), (rect.bottom / 2 - 94));
// Draw the key.
Rectangle(hDC, (rect.right / 2 - 13), (rect.bottom / 2 + 113),
(rect.right / 2 + 13), (rect.bottom / 2 + 50));
Rectangle(hDC, (rect.right / 2 - 13), (rect.bottom / 2 + 96),
(rect.right / 2 + 13), (rect.bottom / 2 + 50));
// Draw the horizontal lines.
MoveToEx(hDC, (rect.right/2 - 150), (rect.bottom / 2 + 0), NULL);
LineTo(hDC, (rect.right / 2 - 16), (rect.bottom / 2 + 0));
MoveToEx(hDC, (rect.right / 2 - 13), (rect.bottom / 2 + 0), NULL);
LineTo(hDC, (rect.right / 2 + 13), (rect.bottom / 2 + 0));
MoveToEx(hDC, (rect.right / 2 + 16), (rect.bottom / 2 + 0), NULL);
LineTo(hDC, (rect.right / 2 + 150), (rect.bottom / 2 + 0));
// Draw the vertical lines.
MoveToEx(hDC, (rect.right/2 + 0), (rect.bottom / 2 - 150), NULL);
LineTo(hDC, (rect.right / 2 + 0), (rect.bottom / 2 - 16));
MoveToEx(hDC, (rect.right / 2 + 0), (rect.bottom / 2 - 13), NULL);
LineTo(hDC, (rect.right / 2 + 0), (rect.bottom / 2 + 13));
MoveToEx(hDC, (rect.right / 2 + 0), (rect.bottom / 2 + 16), NULL);
LineTo(hDC, (rect.right / 2 + 0), (rect.bottom / 2 + 150));
::ReleaseDC(hWnd, hDC);
}
頁面空間到設(shè)備空間的轉(zhuǎn)換是原Windows接口的一部分,也是我們可以用代碼控制的一種轉(zhuǎn)換,頁面空間到設(shè)備空間的轉(zhuǎn)換所用的是兩個(gè)矩形的寬與高的比率,其中頁面空間中的矩形被稱為窗口,設(shè)備空間中的矩形被稱為視口,Windows把窗口原點(diǎn)映射到視口原點(diǎn),把窗口范圍映射到視口范圍,就完成了這種轉(zhuǎn)換。
設(shè)備空間到物理空間的轉(zhuǎn)換有幾個(gè)獨(dú)特之處:它只限于平移,并由Windows的窗口管理部分控制,這種轉(zhuǎn)換的唯一用途是確保設(shè)備空間的原點(diǎn)被映射到物理設(shè)備上的適當(dāng)點(diǎn)上。沒有函數(shù)能設(shè)置這種轉(zhuǎn)換,也沒有函數(shù)可以獲取有關(guān)數(shù)據(jù)。因此我們不能人為干預(yù)。
實(shí)際上我們需要完成的是從頁面空間到物理設(shè)備空間的映射,由于物理設(shè)備空間通常指應(yīng)用程序窗口的客戶區(qū),因此我們只用關(guān)注于從頁面空間到設(shè)備空間的映射就可以了。
一旦應(yīng)用程序建立了設(shè)備描述表(DC),并立即開始調(diào)用GDI繪圖或輸出函數(shù),則運(yùn)用默認(rèn)頁面空間到設(shè)備空間的轉(zhuǎn)換和設(shè)備空間到客戶區(qū)的轉(zhuǎn)換(在應(yīng)用程序調(diào)用SetWorldTransform函數(shù)之前,不會(huì)出現(xiàn)世界坐標(biāo)空間到頁面空間的轉(zhuǎn)換)。
默認(rèn)頁面空間到設(shè)備空間的轉(zhuǎn)換結(jié)果是一對(duì)一的映射;即頁面空間上給出的一點(diǎn)映射到設(shè)備空間的一個(gè)點(diǎn)。正如前文講到的,這種轉(zhuǎn)換沒有以矩陣指定,而是通過把視口寬除以窗口寬,把視口高除以窗口高而得出的。在默認(rèn)的情況下,視口尺寸為1x1個(gè)象素,窗口尺寸為1x1頁單位。
幾乎在所有的GDI函數(shù)中使用的坐標(biāo)值都是采用的邏輯單位。Windows必須將邏輯單位轉(zhuǎn)換為“設(shè)備單位”,即像素。Windows對(duì)所有的消息(如WM_SIZE、WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP),所有的非GDI函數(shù)和一些GDI函數(shù)(例如GetDeviceCaps函數(shù)),永遠(yuǎn)使用設(shè)備坐標(biāo)。“窗口”是基于邏輯坐標(biāo)的,邏輯坐標(biāo)可以是象素、毫米、英寸等單位;“視口”是基于設(shè)備坐標(biāo)(象素)的。通常,視口和客戶區(qū)是相同的。
窗口(邏輯)坐標(biāo)轉(zhuǎn)換為視口(設(shè)備)坐標(biāo)的兩個(gè)公式:
xViewport=(xWindow-xWinOrg)*xViewExt/xWinExt+xViewOrg
yViewport=(yWindow-yWinOrg)*yViewExt/yWinExt+yViewOrg
視口(設(shè)備)坐標(biāo)轉(zhuǎn)換為窗口(邏輯)坐標(biāo)的兩個(gè)公式:
xWindow=(xViewPort-xViewOrg)*xWinExt/xViewExt+xWinOrg
yWindow=(yViewPort-yViewOrg)*yWinExt/yViewExt+yWinOrg
在MM_TEXT映射方式下邏輯坐標(biāo)和設(shè)備坐標(biāo)的相互轉(zhuǎn)換
窗口(邏輯)坐標(biāo)轉(zhuǎn)換為視口(設(shè)備)坐標(biāo)的兩個(gè)公式:
xViewport = xWindow-xWinOrg+xViewOrg
yViewport = yWindow-yWinOrg+yViewOrg
視口(設(shè)備)坐標(biāo)轉(zhuǎn)換為窗口(邏輯)坐標(biāo)的兩個(gè)公式:
xWindow = xViewport-xViewOrg+xWinOrg
yWindow = yViewport-yViewOrg+yWinOrg
CDC中提供了兩個(gè)成員函數(shù)函數(shù)SetViewportOrg和SetWindowOrg,用來改變視口和窗口的原點(diǎn)。
如果將視口原點(diǎn)設(shè)置為(xViewOrg,yViewOrg),則邏輯點(diǎn)(0,0)就會(huì)被映射為設(shè)備點(diǎn)(xViewOrg,yViewOrg)。如果將窗口原點(diǎn)改變?yōu)?xWinOrg,yWinOrg),則邏輯點(diǎn)(xWinOrg,yWinOrg)將會(huì)被映射為設(shè)備點(diǎn)(0,0),即左上角。
不管對(duì)窗口和視口原點(diǎn)作什么改變,設(shè)備點(diǎn)(0,0)始終是客戶區(qū)的左上角。
說一點(diǎn)自己的感悟:
坐標(biāo)系有四種,上面介紹的很詳細(xì)了,就不多說了。
對(duì)于很重要的從頁面坐標(biāo)到設(shè)備坐標(biāo)的轉(zhuǎn)換,頁面坐標(biāo)用邏輯單位表示,可以采用長度度量單位如米或英尺來與邏輯單位相掛鉤,也可以用像素來和邏輯單位掛鉤,而且我們說窗口;設(shè)備坐標(biāo)只用像素表示,而且我們說視口。因此從邏輯坐標(biāo)到設(shè)備坐標(biāo)有很多種映射方式:
Mapping Mode Logical Unit Positive y-axis Extends...
MM_TEXT 1 pixel Downward
MM_HIMETRIC 0.01 mm Upward
MM_TWIPS 1/1440 in Upward
MM_HIENGLISH 0.001 in Upward
MM_LOMETRIC 0.1 mm Upward
MM_LOENGLISH 0.01 in Upward
還有MM_ISOTROPIC(長度和寬度的比例因子一致)和MM_ANISOTROPIC(長寬比例因子可以不一致)。
舉個(gè)例子來說,比如MM_HIMETRIC,每個(gè)邏輯單元代表0.01毫米,比如邏輯橫坐標(biāo)100,代表1毫米的長度,而對(duì)于設(shè)備坐標(biāo)(也可以說物理坐標(biāo))來說,如果分辨率是96dpi,那么也就是1英尺有96個(gè)像素,因此從1毫米先轉(zhuǎn)換為多少英尺,然后再轉(zhuǎn)換為多少個(gè)像素,就可以知道屏幕上在哪個(gè)位置顯示這個(gè)位置了。
還需要注意的是,GDI函數(shù)都是用邏輯坐標(biāo)來計(jì)算的,而非GDI函數(shù)以及少部分GDI函數(shù)都是用設(shè)備坐標(biāo)也就是像素來表示的,設(shè)備坐標(biāo)只能用像素來計(jì)算。