Win32應用程序設計接口(API)使用四種坐標空間:世界坐標系空間、頁面空間、設備空間、和物理設備空間。應用程序運用世界坐標系空間對圖形輸出進行旋轉、斜切或者反射(一般坐標系完不成這些特殊的功能)。
Win32 API把世界坐標系空間和頁面空間稱為邏輯空間;最后一種坐標空間(即物理設備空間)通常指應用程序窗口的客戶區;但是它也包括整個桌面、完整的窗口(包括框架、標題欄和菜單欄)或打印機的一頁或繪圖儀的一頁紙。
如果該應用程序調用了SetWorldTransform函數,那么映射就從應用程序的世界坐標系空間開始;否則,映射在頁面空間中進行。
下面有一個從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);
}
頁面空間到設備空間的轉換是原Windows接口的一部分,也是我們可以用代碼控制的一種轉換,頁面空間到設備空間的轉換所用的是兩個矩形的寬與高的比率,其中頁面空間中的矩形被稱為窗口,設備空間中的矩形被稱為視口,Windows把窗口原點映射到視口原點,把窗口范圍映射到視口范圍,就完成了這種轉換。
設備空間到物理空間的轉換有幾個獨特之處:它只限于平移,并由Windows的窗口管理部分控制,這種轉換的唯一用途是確保設備空間的原點被映射到物理設備上的適當點上。沒有函數能設置這種轉換,也沒有函數可以獲取有關數據。因此我們不能人為干預。
實際上我們需要完成的是從頁面空間到物理設備空間的映射,由于物理設備空間通常指應用程序窗口的客戶區,因此我們只用關注于從頁面空間到設備空間的映射就可以了。
一旦應用程序建立了設備描述表(DC),并立即開始調用GDI繪圖或輸出函數,則運用默認頁面空間到設備空間的轉換和設備空間到客戶區的轉換(在應用程序調用SetWorldTransform函數之前,不會出現世界坐標空間到頁面空間的轉換)。
默認頁面空間到設備空間的轉換結果是一對一的映射;即頁面空間上給出的一點映射到設備空間的一個點。正如前文講到的,這種轉換沒有以矩陣指定,而是通過把視口寬除以窗口寬,把視口高除以窗口高而得出的。在默認的情況下,視口尺寸為1x1個象素,窗口尺寸為1x1頁單位。
幾乎在所有的GDI函數中使用的坐標值都是采用的邏輯單位。Windows必須將邏輯單位轉換為“設備單位”,即像素。Windows對所有的消息(如WM_SIZE、WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP),所有的非GDI函數和一些GDI函數(例如GetDeviceCaps函數),永遠使用設備坐標。“窗口”是基于邏輯坐標的,邏輯坐標可以是象素、毫米、英寸等單位;“視口”是基于設備坐標(象素)的。通常,視口和客戶區是相同的。
窗口(邏輯)坐標轉換為視口(設備)坐標的兩個公式:
xViewport=(xWindow-xWinOrg)*xViewExt/xWinExt+xViewOrg
yViewport=(yWindow-yWinOrg)*yViewExt/yWinExt+yViewOrg
視口(設備)坐標轉換為窗口(邏輯)坐標的兩個公式:
xWindow=(xViewPort-xViewOrg)*xWinExt/xViewExt+xWinOrg
yWindow=(yViewPort-yViewOrg)*yWinExt/yViewExt+yWinOrg
在MM_TEXT映射方式下邏輯坐標和設備坐標的相互轉換
窗口(邏輯)坐標轉換為視口(設備)坐標的兩個公式:
xViewport = xWindow-xWinOrg+xViewOrg
yViewport = yWindow-yWinOrg+yViewOrg
視口(設備)坐標轉換為窗口(邏輯)坐標的兩個公式:
xWindow = xViewport-xViewOrg+xWinOrg
yWindow = yViewport-yViewOrg+yWinOrg
CDC中提供了兩個成員函數函數SetViewportOrg和SetWindowOrg,用來改變視口和窗口的原點。
如果將視口原點設置為(xViewOrg,yViewOrg),則邏輯點(0,0)就會被映射為設備點(xViewOrg,yViewOrg)。如果將窗口原點改變為(xWinOrg,yWinOrg),則邏輯點(xWinOrg,yWinOrg)將會被映射為設備點(0,0),即左上角。
不管對窗口和視口原點作什么改變,設備點(0,0)始終是客戶區的左上角。
說一點自己的感悟:
坐標系有四種,上面介紹的很詳細了,就不多說了。
對于很重要的從頁面坐標到設備坐標的轉換,頁面坐標用邏輯單位表示,可以采用長度度量單位如米或英尺來與邏輯單位相掛鉤,也可以用像素來和邏輯單位掛鉤,而且我們說窗口;設備坐標只用像素表示,而且我們說視口。因此從邏輯坐標到設備坐標有很多種映射方式:
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(長寬比例因子可以不一致)。
舉個例子來說,比如MM_HIMETRIC,每個邏輯單元代表0.01毫米,比如邏輯橫坐標100,代表1毫米的長度,而對于設備坐標(也可以說物理坐標)來說,如果分辨率是96dpi,那么也就是1英尺有96個像素,因此從1毫米先轉換為多少英尺,然后再轉換為多少個像素,就可以知道屏幕上在哪個位置顯示這個位置了。
還需要注意的是,GDI函數都是用邏輯坐標來計算的,而非GDI函數以及少部分GDI函數都是用設備坐標也就是像素來表示的,設備坐標只能用像素來計算。