第五章 坐標(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é)果。每?jī)蓚€(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ā)生變化。現(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>
|