第五章 坐標(biāo)系統(tǒng)與坐標(biāo)變換
by iwaswzq 2005/1/22
經(jīng)常有朋友提問關(guān)于編程過程中遇到的坐標(biāo)變換問題。我抽了點(diǎn)時(shí)間從msdn摘譯了一些東西,并加了一些自己的理解,希望能有助于對(duì)程序中坐標(biāo)變換的理解。鑒于我水平有限,可能某些概念的理解有些錯(cuò)誤或者解釋不夠準(zhǔn)確,歡迎指正。不足的地方,以后有時(shí)間會(huì)繼續(xù)豐富此文。
win32程序使用坐標(biāo)系統(tǒng)之間的變換完成圖形的縮放、旋轉(zhuǎn)、平移等輸出操作。win32下面總共使用四個(gè)坐標(biāo)空間:世界坐標(biāo)系、頁面坐標(biāo)系、設(shè)備坐標(biāo)系和物理坐標(biāo)系(包括客戶區(qū)、桌面或打印紙等)。每個(gè)坐標(biāo)空間都是一個(gè)線性空間,用兩個(gè)相互垂直的坐標(biāo)軸定位兩維的物體。
我們把改變一個(gè)物體的大小、方向和形狀的算法稱作“變換”。一個(gè)圖形物體從一個(gè)坐標(biāo)空間映射到另一個(gè)坐標(biāo)空間的過程就是一個(gè)變換。最終,物體顯示在一個(gè)物理設(shè)備上,通常是屏幕或者打印機(jī)。
1、四個(gè)坐標(biāo)系的定義
坐標(biāo)系 描述
世界坐標(biāo)系 可選,用于圖形轉(zhuǎn)換的起始坐標(biāo)空間。最大尺寸是 2^32單位高和 2^32 單位寬。
支持縮放、平移、旋轉(zhuǎn)、變形、投射等轉(zhuǎn)換操作。
頁面坐標(biāo)系 作為世界坐標(biāo)系之后的第二個(gè)坐標(biāo)系使用,也可以作為變換的起始坐標(biāo)系。
最大尺寸是 2^32單位高和 2^32 單位寬??梢栽O(shè)置映射模式。
設(shè)備坐標(biāo)系 用于頁面坐標(biāo)系之后。僅僅允許平移操作。保證設(shè)備坐標(biāo)系的原點(diǎn)位于正確的
物理設(shè)備空間中合適的位置上。最大尺寸是2^27單位高和 2^27單位寬。
物理坐標(biāo)系 圖形變換后的最終的輸出空間。通常指程序窗口的客戶區(qū)。也可以是整個(gè)桌面、
整個(gè)窗口區(qū)域或者打印機(jī)、繪圖儀的某一頁,取決于程序獲得的DC的句柄。物
理設(shè)備的尺寸取決于顯卡、打印機(jī)等的設(shè)置。
頁面空間和設(shè)備空間一起工作,在這兩個(gè)空間下,程序可以使用設(shè)備相關(guān)的單位,例如毫米和英寸。但是在世界坐標(biāo)系和頁面坐標(biāo)系下,都認(rèn)為是邏輯坐標(biāo)系,單位是邏輯單位1。
任何程序的繪圖代碼中使用的坐標(biāo)都是從世界坐標(biāo)系開始,直到物理坐標(biāo)系,最后得到(看到)輸出結(jié)果。每兩個(gè)坐標(biāo)系之間,系統(tǒng)都采用一種變換方法,從前面的坐標(biāo)空間復(fù)制(或者映射)一個(gè)矩形區(qū)域到下一個(gè)坐標(biāo)空間。為了便于處理,如果程序調(diào)用了SetWorldTransform函數(shù),則這個(gè)映射過程從世界坐標(biāo)系開始,否則從頁面坐標(biāo)系開始。用圖形表示如下:
+-----------+ +------------+ +------------+ +------------------+
| 繪圖代碼 | | (窗口) | | (視口) | | (屏幕/打印機(jī)等) |
| 世界坐標(biāo)系| ---〉 | 頁面坐標(biāo)系 | -----〉| 設(shè)備坐標(biāo)系 | ----〉| 物理坐標(biāo)系 |
| | | | | | | |
+-----------+ +------------+ +------------+ +------------------+
舉個(gè)例子說,如果我們?cè)贒C上面有個(gè)畫線函數(shù):
MoveTo(hDC, 0, 0);
LineTo(hDC, 10,10);
則如果我們沒有使用SetWordTransform函數(shù),可以認(rèn)為我們的畫線操作就是在頁面坐標(biāo)系下面,0,10都是頁面坐標(biāo)系下的坐標(biāo)。如果我們使用了SetWorldTransform函數(shù),則畫線操作是在世界坐標(biāo)系下面。頁面坐標(biāo)系中對(duì)應(yīng)的這個(gè)線段是世界坐標(biāo)系下面的線段(0,0) -> (10,10) 通過兩個(gè)坐標(biāo)空間之間的變換矩陣變換得到,結(jié)果可能還是(0,0)點(diǎn)到(10,10)點(diǎn),也可能是(5,9)點(diǎn)到(-20,14)點(diǎn),這取決于兩個(gè)坐標(biāo)系之間的變換矩陣。
同樣道理,對(duì)于上述四個(gè)坐標(biāo)系,當(dāng)系統(tǒng)從一個(gè)坐標(biāo)系中復(fù)制指定矩形區(qū)域內(nèi)的某個(gè)點(diǎn)到下一個(gè)坐標(biāo)系時(shí),它使用這兩個(gè)坐標(biāo)系之間的變換算法,根據(jù)點(diǎn)的原坐標(biāo)計(jì)算得到點(diǎn)的像坐標(biāo)。因此,一個(gè)圖形在不同坐標(biāo)系下,其尺寸、方向和形狀都可能不同。注意一點(diǎn),雖然這個(gè)變換是兩個(gè)坐標(biāo)系之間的,是針對(duì)物體整體而言的變換,但在系統(tǒng)在操作的時(shí)候,是逐點(diǎn)、逐行操作的。
雖然很少用到SetWorldTransform函數(shù),但是應(yīng)該掌握最基本的坐標(biāo)系之間的線性變換矩陣,形式如下:
| eM11 eM12 0 |
| eM21 eM22 0 | 即: x' = x * eM11 + y * eM21 + eDx,
| eDx eDy 1 | y' = x * eM12 + y * eM22 + eDy,
函數(shù)采用邏輯單位,缺省的變換矩陣是單位陣:
| 1 0 0 |
| 0 1 0 | 變換關(guān)系就是 x' = x ; y' = y ; 相當(dāng)于沒有變換
| 0 0 1 |
四個(gè)eM參數(shù)給出旋轉(zhuǎn)和縮放變換系數(shù),eDx/eDy給出平移變換系數(shù)。
注意SetWorldTransform函數(shù)要求DC的圖形模式是GM_ADVANCED,可以用SetGraphicsMode設(shè)置,僅Windows NT/ 2000下面支持。缺省圖形模式是GM_COMPATIBLE,兼容16位windows,這個(gè)模式下不能使用該函數(shù)。下面簡(jiǎn)單介紹這兩個(gè)坐標(biāo)系之間的變換矩陣。
2、世界坐標(biāo)系到頁面坐標(biāo)系的變換
(1)平移。物體上每個(gè)點(diǎn)進(jìn)行水平和垂直的移動(dòng),eDx 和eDy 參數(shù)分別給出移動(dòng)的尺寸。具體算法是:
x' = x + Dx
y' = y + Dy
其中 x',y' 是新的坐標(biāo), x,y 是源坐標(biāo)。 Dx是水平移動(dòng)距離,Dy是垂直移動(dòng)距離。
平移矩陣是:
|1 0 0|
|x' y' 1| = |x y 1| * |0 1 0|
|Dx Dy 1|
(2)縮放。組成物體的每個(gè)水平行或者垂直行進(jìn)行拉伸或者壓縮。算法公式是:
y' = y * Dy
x' = x * Dx
其中 Dy,Dx是縮放系數(shù)。用矩陣表示為:
|x' y' 1| = |x y 1| * |Dx 0 0|
|0 Dy 0|
|0 0 1|
(3)旋轉(zhuǎn)。組成物體的每個(gè)點(diǎn)都相對(duì)于坐標(biāo)原點(diǎn)旋轉(zhuǎn)一個(gè)角度。算法公式是:
x' = (x * cos A) - (y * sin A)
y' = (x * sin A) + (y * cos A)
A表示繞原點(diǎn)逆時(shí)針旋轉(zhuǎn)的角度,用矩陣表示如下:
|x' y' 1| = |x y 1| * | cos A sin A 0|
|-sin A cos A 0|
| 0 0 1|
(4)變形。分為水平變形和垂直變形兩種,舉個(gè)例子說,一個(gè)矩形通過變形成為一個(gè)平行四邊形。算法公式分別是:
x' = x + (Sx * y)
y' = y + (Sy * x)
其中Sx, Sy分別是變形系數(shù)。用矩陣表示為:
|x' y' 1| = |x y 1| * | 1 Sx 0|
| Sy 1 0|
| 0 0 1|
(5)鏡像映射。例如水平翻轉(zhuǎn)的公式是:
x' = –x
用矩陣表示是:|-1 0|
|0 1|
線性變換可以是上面幾種變換中任意若干種的組合。最終的變換矩陣是一個(gè)3x3的矩陣??梢哉{(diào)用CombineTransform進(jìn)行兩種變換的組合,也可以自己按照公式計(jì)算出變換矩陣。
強(qiáng)調(diào)一點(diǎn),雖然可以通過SetWorldTransform函數(shù)調(diào)用設(shè)置世界坐標(biāo)系到頁面坐標(biāo)系的變換矩陣,但通常情況下,在我們的程序中,圖形圖像的變換并不是通過系統(tǒng)來完成的。而是程序自己完成的,因?yàn)樽约鹤觯梢愿屿`活,容易控制,效率更高。因此不推薦使用SetWorldTransfor函數(shù),如果我們要做圖形變換的顯示,建議自己先用變換算法計(jì)算好,然后直接在頁面坐標(biāo)系下面作圖。
3、頁面坐標(biāo)系到設(shè)備坐標(biāo)系之間的變換
這個(gè)變換決定了與特定DC相聯(lián)系的映射模式,影響該DC上的所有圖形輸出。映射模式本身就是一個(gè)縮放變換,決定了畫圖操作中一個(gè)單位的尺寸,映射模式也可以用于平移變換,某些情形下,映射模式會(huì)改變x,y軸的坐標(biāo)原點(diǎn)。首先來了解幾個(gè)映射模式:
(1)映射模式說明
-------------------------------------------------------------------------
映射模式 描述
-------------------------------------------------------------------------
MM_ANISOTROPIC 每個(gè)頁面空間的單位映射為程序定義的設(shè)備空間的單位。兩個(gè)坐標(biāo)
軸的縮放尺寸可以不一致(例如,一個(gè)世界坐標(biāo)系下面的園在指定
設(shè)備上可能顯示為橢圓)。坐標(biāo)軸的方向也是程序定義的。
MM_HIENGLISH 每個(gè)頁面空間的單位映射成設(shè)備空間中的0.001英寸。x軸向右,y軸向上。
MM_HIMETRIC 每個(gè)頁面空間的單位映射成設(shè)備空間中的0.01毫米。x軸向右,y軸向上。
MM_ISOTROPIC 每個(gè)頁面空間的單位映射為程序定義的設(shè)備空間的單位。兩個(gè)坐標(biāo)軸
的縮放尺寸一樣。坐標(biāo)軸的方向由程序定義。
MM_LOENGLISH 每個(gè)頁面空間的單位映射成設(shè)備空間中的0.01英寸。x軸向右,y軸向上。
MM_LOMETRIC 每個(gè)頁面空間的單位映射成設(shè)備空間中的0.1毫米。x軸向右,y軸向上。
MM_TEXT 每個(gè)頁面空間的單位映射成一個(gè)像素。就是說無縮放。如果也沒有平移變換,
則本映射模式下的頁面空間和物理設(shè)備坐標(biāo)空間等價(jià)。x軸向右,y軸向下。
MM_TWIPS 每個(gè)頁面空間的單位映射成打印機(jī)點(diǎn)的1/20(1/1440英寸)。x軸向右,y軸向上。
------------------------------------------------------------------------------
要設(shè)置映射模式,調(diào)用SetMapMode,要獲得當(dāng)前的映射模式,調(diào)用GetMapMode.
頁面空間到設(shè)備空間的變換涉及到窗口或者視口中的點(diǎn),從這個(gè)意義上講,窗口反映了頁面空間中的邏輯坐標(biāo)系統(tǒng),而視口代表設(shè)備空間的設(shè)備坐標(biāo)系統(tǒng)。窗口和視口都包含一個(gè)坐標(biāo)原點(diǎn)和x/y軸。窗口中使用的參數(shù)是邏輯坐標(biāo),視口中使用的參數(shù)是設(shè)備坐標(biāo)(像素)。系統(tǒng)根據(jù)坐標(biāo)原點(diǎn)生成變換矩陣。這就意味著,窗口和視口分別負(fù)責(zé)給出從頁面坐標(biāo)空間到設(shè)備坐標(biāo)空間映射變換矩陣的一半?yún)?shù)。
根據(jù)窗口和視口的坐標(biāo)軸尺寸(最大坐標(biāo)值),可以建立一個(gè)比例或者縮放系數(shù),用于頁面空間到設(shè)備空間的變換。對(duì)于上面六種預(yù)定一映射模式,當(dāng)調(diào)用SetMapMode函數(shù)的時(shí)候,坐標(biāo)軸的最大尺寸是由系統(tǒng)設(shè)置的,無法更改。其他兩種映射模式MM_ISOTROPIC和 MM_ANISOTROPIC下,需要定義坐標(biāo)軸的最大尺寸,因此調(diào)用SetMapMode之后,必須調(diào)用SetWindowExtEx和SetViewportExtEx進(jìn)行設(shè)置。特別是在MM_ISOTROPIC映射模式下。必須注意先調(diào)用SetWindowExtEx然后調(diào)用SetViewportExtEx。
根據(jù)窗口和視口的坐標(biāo)原點(diǎn),可以建立頁面空間到設(shè)備空間的線性變換的平移關(guān)系。通過函數(shù)SetWindowOrgEx和SetViewportOrgEx來設(shè)置原點(diǎn)。坐標(biāo)原點(diǎn)和坐標(biāo)軸的尺寸沒有關(guān)系,因此任何映射模式下都可以設(shè)置坐標(biāo)原點(diǎn),改變映射模式也不會(huì)影響當(dāng)前的坐標(biāo)原點(diǎn)。由于這兩個(gè)函數(shù)是相關(guān)的,所以通常使用一個(gè)即可,不必兩個(gè)都調(diào)用。記住一點(diǎn),無論是否調(diào)用這兩個(gè)函數(shù),設(shè)備坐標(biāo)(0,0)永遠(yuǎn)是左上角。也可以用函數(shù)OffsetWindowOrgEx或者OffsetViewportOrgEx改變坐標(biāo)原點(diǎn)。下面的公式給出了頁面坐標(biāo)空間到設(shè)備坐標(biāo)空間之間的點(diǎn)的映射關(guān)系:
Dx = ((Lx - WOx) * VEx / WEx) + VOx
Dx 設(shè)備空間中的點(diǎn)(或者說單位)
Lx 邏輯單位 x (或者說頁面空間中的單位)
WOx 窗口的 x 原點(diǎn)
VOx 視口中 x 原點(diǎn)
WEx 窗口的 x軸尺寸
VEx 視口的 x軸尺寸
對(duì)于y方向的公式也是一樣的。
函數(shù)LPtoDP 和 DPtoLP可以用來完成兩個(gè)坐標(biāo)空間點(diǎn)的變換的計(jì)算。
(2)關(guān)于預(yù)定義映射模式
6種預(yù)定義映射模式中,只有MM_TEXT是設(shè)備相關(guān)的,其余都是設(shè)備無關(guān)的。缺省的映射模式是MM_TEXT,即一個(gè)邏輯單位等于一個(gè)像素。邏輯到設(shè)備的映射僅僅是一個(gè)平移關(guān)系,和程序設(shè)置的窗口和視口的坐標(biāo)原點(diǎn)有關(guān)。視口和窗口的坐標(biāo)軸尺寸都設(shè)置成1,從而形成一一映射。如果程序要顯示準(zhǔn)確的幾何圖形,可以使用MM_LOENGLIST模式,保證圖形的形狀在任何顯示器和打印機(jī)下面都是一樣的。如果仍然使用MM_TEXT,則可能在VGA顯示器上面顯示的一個(gè)圓,到了EGA顯示器下就變成橢圓了,如果用300dpi的激光打印機(jī)輸出,則結(jié)果是很小的一個(gè)圓。
(3)自定義映射模式
MM_ISOTROPIC 和 MM_ANISOTROPIC兩個(gè)映射模式用于自定義。MM_ISOTROPIC可以保證邏輯單位在x和y方向是一致的。MM_ANISOTROPIC下允許設(shè)置成不同。例如一個(gè) CAD 或者繪圖程序可以用MM_ISOTROPIC模式,將邏輯單位設(shè)置成工程常用的1/64英寸,代碼如下:
SetMapMode(hDC, MM_ISOTROPIC);
SetWindowExtEx(hDC, 64, 64, NULL);
SetViewportExtEx(hDC, GetDeviceCaps(hDC, LOGPIXELSX),
GetDeviceCaps(hDC, LOGPIXELSY), NULL);
4、設(shè)備坐標(biāo)空間到物理設(shè)備的變換
設(shè)備空間到物理設(shè)備的變換在很多情況下是唯一的,是由系統(tǒng)控制的,主要的目的是保證設(shè)備坐標(biāo)系的原點(diǎn)準(zhǔn)確映射到物理設(shè)備的合適位置。比如屏幕上面顯示的某個(gè)程序,窗口顯示的位置要和窗口矩形在顯存中的位置對(duì)應(yīng)起來,移動(dòng)窗口就是改變這個(gè)控制窗口輸出的矩形在顯存中的起點(diǎn),反映在顯示器屏幕上面就是程序窗口的移動(dòng)。如果是在打印機(jī)dc上面畫圖,則物理設(shè)備就是紙張了。這個(gè)變換通常是由系統(tǒng)負(fù)責(zé)控制的。因此沒有函數(shù)用于設(shè)置這個(gè)變換,也沒有函數(shù)獲取相關(guān)的變換數(shù)據(jù)。
5、缺省的變換
如果程序創(chuàng)建了一個(gè)DC,并立刻開始調(diào)用GDI繪圖函數(shù),使用的變換過程就是:
缺省的頁面空間 -〉設(shè)備空間 -〉客戶區(qū)(物理設(shè)備空間)的變換。
除非程序調(diào)用SetGraphicsMode 和SetWorldTransform函數(shù),否則世界坐標(biāo)空間-〉頁面坐標(biāo)空間是單位變換,可以認(rèn)為沒有變換。
頁面空間-〉設(shè)備空間在MM_TEXT模式下是一一映射,即給定頁面空間中的一點(diǎn),對(duì)應(yīng)設(shè)備空間中的相同點(diǎn)。前面已經(jīng)指出,這個(gè)變換不是通過一個(gè)矩陣,而是通過用視口的寬度/窗口的寬度,視口的高度/窗口的高度兩個(gè)公式來計(jì)算的。缺省情況下,視口的尺寸是1X1像素。窗口的尺寸是1X1頁。
設(shè)備空間-〉物理設(shè)備(客戶區(qū),桌面或者打印機(jī))的變換通常也是一一映射,即設(shè)備空間中的一點(diǎn)對(duì)應(yīng)客戶區(qū)或者打印機(jī)輸出中的一個(gè)單位,目的是保證無論程序窗口如何在屏幕上面移動(dòng),最終的輸出始終準(zhǔn)確地反映設(shè)備空間中的圖形。
注意MM_TEXT模式比較特殊,它的Y坐標(biāo)軸是向下的,其它映射模式的Y坐標(biāo)軸都是向上的。
6、下面用一個(gè)例子考察每個(gè)坐標(biāo)變換函數(shù)的意義。用classwizard生成一個(gè)sdi工程,視類選擇從CScrollView派生,然后添加如下代碼:
void CSdiscroView::OnDraw(CDC* pDC)
{
//用綠色填充一個(gè)圓形區(qū)域(中心[200,200],半徑150)
CRect rect;
rect.SetRect(50,50,350,350);
CBrush bru;
bru.CreateSolidBrush(RGB(0,127,0));
CBrush *pBrushOld = pDC->SelectObject(&bru);
pDC->Ellipse(&rect);
pDC->SelectObject(pBrushOld);
bru.DeleteObject();
//輸出坐標(biāo)原點(diǎn)
pDC->TextOut(0,0,"(0,0)");
//畫出坐標(biāo)軸
pDC->MoveTo(0,0);
pDC->LineTo(500,0); //x軸
pDC->LineTo(490,5); //箭頭
pDC->MoveTo(500,0);
pDC->LineTo(490,-5);
pDC->MoveTo(0,0);
pDC->LineTo(0,500); //y軸
pDC->LineTo(5,490);
pDC->MoveTo(0,500);
pDC->LineTo(-5,490);
}
void CSdiscroView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal;
//設(shè)置整個(gè)窗口尺寸為1000x1000
sizeTotal.cx = sizeTotal.cy = 1000;
SetScrollSizes(MM_TEXT, sizeTotal);
}
實(shí)際上我們所有的畫圖操作都是在世界坐標(biāo)系下面,由于沒有使用SetWorldTransform,所以世界坐標(biāo)系和頁面坐標(biāo)系等價(jià),可以認(rèn)為我們的畫圖操作就是在頁面坐標(biāo)系中。下面分別添加相應(yīng)的函數(shù)調(diào)用,考察每個(gè)函數(shù)對(duì)輸出的影響。
1、SetWindowOrg / SetViewportOrg (CDC類成員函數(shù),對(duì)應(yīng)api函數(shù)SetWindowOrgEx / SetViewportOrgEx
void CSdiscroView::OnDraw(CDC* pDC)
{
pDC->SetWindowOrg(-100,-50);//或者 pDC->SetViewportOrg(100,50);
//
... //原來的代碼
}
窗口沒有滾動(dòng)之前,視口坐標(biāo)系的原點(diǎn)和窗口坐標(biāo)系的原點(diǎn)重合,如果滾動(dòng)窗口,相當(dāng)于改變視口原點(diǎn)的位置,因此顯示在屏幕上面的部分(視口里面的東西)就發(fā)生變化?,F(xiàn)在我們不滾動(dòng)窗口,而是調(diào)用SetWindowOrg改變窗口原點(diǎn)的坐標(biāo),看看發(fā)生的變化。
SetWindowOrg(-100,-50)函數(shù)調(diào)用的意思是把窗口(頁面坐標(biāo)系)中的(-100,-50)點(diǎn)設(shè)置成窗口的原點(diǎn),由于窗口和視口的原點(diǎn)永遠(yuǎn)是重合的,所以視口的(0,0)點(diǎn)現(xiàn)在就和窗口的(-100,-50)重合,而視口的(0,0)點(diǎn)就是程序客戶區(qū)的左上角,因此設(shè)置的后果就是:繪圖的輸出向x/y軸的正方向移動(dòng)了。編譯運(yùn)行以后可以看到,字符串"(0,0)"向右下平移了,好像向上、向左滾動(dòng)了窗口一樣。同樣道理,如果SetWindowOry里面使用的是(100,50),則效果等同于向下、向右滾動(dòng)了窗口。
對(duì)應(yīng)圖形如下:
/-----視口的原點(diǎn)
(-100,-50) +-------------+
/-----視口的原點(diǎn) | (視口) |
+-------------+-------+---->頁面坐標(biāo)系x + +--------+-----------+----->頁面坐標(biāo)系x
|(0,0) | | | | (0,0) | |
| | | | | | |
| (視口) | | | | | |
| (屏幕上的) | | | | | |
| (部分 ) | | 設(shè)置SetWindowOrg后 | | | |
| | | ---------------------〉+----+--------+ |
+-------------+ | | |
| | | |
| 整個(gè)窗口比視口大 | | 窗口 |
| 有些部分需要滾動(dòng) | | |
| 才能顯示出來 | | |
+---------------------+ +--------------------+
| |
\/ Y \/ Y
從效果上SetWindowOrg(-100,-50)和SetViewportOrg(100,50)是等價(jià)的。但是使用一下就會(huì)發(fā)現(xiàn)SetViewportOrg(100,50)以后如果滾動(dòng)窗口的話,窗口的刷新有些問題,所以在CScrollView里面用SetWindowOrg比較好,對(duì)于非滾動(dòng)形式的窗口,使用SetViewportOrg比較直觀一些。窗口對(duì)應(yīng)的就是頁面坐標(biāo)系,也就是邏輯坐標(biāo)系,視口對(duì)應(yīng)的是設(shè)備坐標(biāo)系。
2、關(guān)于映射模式,上面的例子用的是缺省的映射模式MM_TEXT,現(xiàn)在改變一下映射模式,看看有什么變化。去掉設(shè)置原點(diǎn)的代碼,加上:
void CSdiscroView::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_LOMETRIC);
//用綠色填充一個(gè)圓形區(qū)域(中心[200,200],半徑150)
... //原來代碼不變
}
運(yùn)行一下看看,怎么Y坐標(biāo)軸和圓都不見了,原來這個(gè)模式下面,Y軸是向上的。把程序里面的Y坐標(biāo)都改成負(fù)值:
void CSdiscroView::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_LOMETRIC);
//用綠色填充一個(gè)圓形區(qū)域(中心[200,200],半徑150)
CRect rect;
rect.SetRect(50,-50,350,-350);
...
//用藍(lán)色輸出坐標(biāo)原點(diǎn)
pDC->TextOut(0,0,"(0,0)");
//畫出坐標(biāo)軸
...
pDC->MoveTo(0,0);
pDC->LineTo(0,-500); //y軸
pDC->LineTo(5,-490);
pDC->MoveTo(0,-500);
pDC->LineTo(-5,-490);
}
運(yùn)行一看,OK都出來了,但是尺寸比原來小多了。原來每個(gè)邏輯單位被映射成0.1毫米。那么圓的直徑是300,應(yīng)該對(duì)應(yīng)30毫米,用尺子在屏幕上面量一下吧,幾乎就是30毫米啊:)。不相信,設(shè)置成MM_HIMETRIC,天啊,看不到了,可能太小了?把圓的半徑加大:
rect.SetRect(50,-50,1350,-1350);
嗯,出來了,直徑好像是1300*0.01 = 13毫米。既然這樣MM_HIENGLISH和MM_LOENGLISH以及MM_TWIPS就不測(cè)試了。需要注意一點(diǎn),SetMapMode函數(shù)調(diào)用后,僅僅影響后續(xù)的畫圖函數(shù),而它前面的畫圖函數(shù)仍然按照原來的映射模式輸出。所以同一個(gè)繪圖函數(shù)中,可以調(diào)用多次SetMapMode改變映射模式,比如繪圖單位在英寸和厘米之間,繪圖的精度在0.01厘米和0.1厘米之間可以時(shí)刻根據(jù)需要進(jìn)行切換。
3、SetWindowExt 和 SetViewportExt。由于它們僅僅在MM_ISOTROPIC 模式下有效,所以這樣做:
void CSdiscroView::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_ISOTROPIC );
CSize sizeOrg = pDC->SetWindowExt(200,100);
//查看返回值可以發(fā)現(xiàn)SetViewportExt返回的是當(dāng)前屏幕設(shè)置的分辨率1024 x 768,不過y是負(fù)值
//因?yàn)镸M_ISOTROPIC模式下,Y軸是向下的。所以記得所有畫圖代碼中的Y坐標(biāo)用正值!
sizeOrg =pDC->SetViewportExt(200,100);
...//畫圖代碼
}
通過改變兩個(gè)Set函數(shù)中的參數(shù)值,發(fā)現(xiàn)系統(tǒng)自動(dòng)管理x/y的比率關(guān)系,使圓形保持正確形狀。而且圖形的大小和參數(shù)有關(guān):
假設(shè)SetWindowExt(xWin,yWin); SetViewportExt(xView,yView);則系統(tǒng)使用xView/xWin , yView/yWin 兩個(gè)比值中較小的一個(gè)作為x/y兩個(gè)方向共同的壓縮比例。最后圖形的大小僅僅和這個(gè)縮放系數(shù)有關(guān)。如果兩個(gè)系數(shù)都大于1,則系統(tǒng)使用1:1比例,并不放大圖形。
4、DPtoLP 和 LPtoDP 。這兩個(gè)函數(shù)用于邏輯坐標(biāo)和設(shè)備坐標(biāo)之間的轉(zhuǎn)換。在MM_TEXT模式下兩個(gè)坐標(biāo)是一樣的。現(xiàn)在設(shè)置成MM_LOMETRIC模式,看看它們的作用。
void CSdiscroView::OnDraw(CDC* pDC)
{
//每個(gè)邏輯單位對(duì)應(yīng)0.1毫米設(shè)備單位
pDC->SetMapMode(MM_LOMETRIC );
CPoint p(100,200);
pDC->DPtoLP(&p,1);
CString str;
str.Format(" Use DPtoLP : (100,200) -> (%d,%d)\n",p.x,p.y);
TRACE(str);
p.x = 100; p.y = 200;
pDC->LPtoDP(&p,1);
str.Format(" Use LPtoDP : (100,200) -> (%d,%d)\n",p.x,p.y);
TRACE(str);
}
//調(diào)試窗口輸出
Use DPtoLP : (100,200) -> (313,-625)
Use LPtoDP : (100,200) -> (32,-64)
可見設(shè)備坐標(biāo)系中的(100,200)對(duì)應(yīng)的是邏輯坐標(biāo)系(窗口)中的(313,-625)一點(diǎn),邏輯坐標(biāo)系下面的(100,200)對(duì)應(yīng)設(shè)備坐標(biāo)系下面的(32,-64)點(diǎn)。注意這個(gè)變換結(jié)果是設(shè)備相關(guān)的。對(duì)于不同的dc得到不同的結(jié)果。設(shè)置相同的都用屏幕dc,用不同計(jì)算機(jī)測(cè)試,不同的顯示器,不同的顯示模式設(shè)置也會(huì)得到不同的變換結(jié)果。
這是什么意思呢?就是說窗口中,邏輯坐標(biāo)(313,-625)在MM_LOMETRIC模式下,對(duì)應(yīng)設(shè)備坐標(biāo)系中X方向距離原點(diǎn)31.3毫米,Y方向距離原點(diǎn)62.5毫米的一個(gè)點(diǎn),那個(gè)點(diǎn)在設(shè)備坐標(biāo)系中坐標(biāo)是(100,200),其實(shí)就是MM_TEXT模式下,邏輯坐標(biāo)系下面的那個(gè)(100,200)點(diǎn)。同樣道理,邏輯坐標(biāo)(100,200)點(diǎn),映射到設(shè)備坐標(biāo)系中,是x軸方向距離原點(diǎn)10mm,y軸方向距離原點(diǎn)-20mm的點(diǎn)(注意方向),那個(gè)點(diǎn)的邏輯坐標(biāo)是(32,-64)。也就是MM_TEXT模式下邏輯坐標(biāo)系中的(32,-64)點(diǎn)。
最后要說明一點(diǎn),OnEraseBkgnd(CDC* pDC)里面的DC和OnDraw(CDC* pDC)里面的DC有所不同啊。窗口的滾動(dòng)對(duì)前者沒有影響,也就是說無論窗口如何滾動(dòng),在OnEraseBkgnd函數(shù)中輸出的東西永遠(yuǎn)在視口固定的位置上,不受滾動(dòng)影響。所以畫圖的時(shí)候,不要把背景和前景混淆了,什么函數(shù)就是干什么工作。
-------------------------------------------------------------
End. iwaswzq 2005/1/22
-------------------------------------------------------------
</PRE>
評(píng)論
數(shù)據(jù)是亂碼?
聯(lián)系客服