什麼是映射模式呢?為了說清楚這個概念,我們先介紹兩個名詞:“視窗”、“視口”。
視口是基於裝置座標的。對於顯示器,就是像素的,也就是你看到的。而視窗是基於邏輯座標的,虛擬,也是你寫程式時使用的。而且與你當前拿到的裝置描述表有關,一般通過beginPaint拿到的都是客戶區;而使用getDC拿到的則是通常意義下的視窗:客戶區+功能表列+工具列+標題列等等。
而視窗到視口的座標映射,就是映射模式。用數學公式表述為:
視窗到視口:
xViewport = (xWindow-xWinOrg)*xViewExt/xWinExt+xViewOrg
yViewport = (yWindow-yWinOrg)*yViewExt/yWinExt+yViewOrg
其中 xViewExt表示的是視口的橫座標範圍,xWinExt表示的是視窗的橫座標範圍,通常我們關心的不是它們的具體值,而是二者的比例。
windows提供了8種映射模式
映射方式 |
邏輯單位 |
增加值 |
x值 |
y值 |
MM_TEXT |
圖素 |
右 |
下 |
MM_LOMETRIC |
0.1 mm |
右 |
上 |
MM_HIMETRIC |
0.01 mm |
右 |
上 |
MM_LOENGLISH |
0.01 in. |
右 |
上 |
MM_HIENGLISH |
0.001 in. |
右 |
上 |
MM_TWIPS |
1/1440 in. |
右 |
上 |
MM_ISOTROPIC |
任意(x = y) |
可選 |
可選 |
MM_ANISOTROPIC |
任意(x != y) |
可選 |
可選 |
這8種模式通常分為3大類:MM_TEXT、“度量”映射方式、“自作主張”的映射方式。下面對他們一一進行介紹:
MM_TEXT
之所以稱為MM_TEXT,是因為這個方式在預設情況下與我們看書的方式相同:原點在左上方,從左向右,從上到下。
對於MM_TEXT映射方式,內定的原點和範圍如下所示:
視窗原點:(0, 0) 可以改變
視口原點:(0, 0) 可以改變
視窗範圍:(1, 1) 不可改變
視口範圍:(1, 1) 不可改變
這意味著視窗座標與邏輯座標之間沒有比例變換,只有原點設定引起的平移變換。所以它也稱為稱為“全約束”的映射方式。你在視窗位置中+1,則在螢幕上移動一個像素。
你可以通過SetViewportOrgEx和SetWindowOrgEx,用來改變視口和視窗的原點,但一般我們只改變一個就能達到滿意的效果,兩個同時改變則容易產生混亂。
通過GetViewportOrgEx和GetWindowOrgEx來擷取目前視連接埠和視窗的原點。
舉一個例子,假如我們要繪製正弦曲線,可以:(完整的程式見於:windows程式設計(6):基本畫圖
http://blog.csdn.net/thefutureisour/article/details/7576712)
case WM_PAINT:hdc = BeginPaint(hwnd,&ps);//畫一條直線MoveToEx(hdc,0,cyClient/2,NULL);LineTo(hdc,cxClient,cyClient/2);//繪製正弦曲線for(int i = 0; i< NUM;i++){//把x軸等分成1000份apt[i].x = i * cxClient / NUM;apt[i].y = (int) (cyClient / 2 * (1-sin(TWOPI * i /NUM)));LineTo(hdc,apt[i].x,apt[i].y);//Sleep(10);}EndPaint(hwnd,&ps);return 0;
我們可以改變視口的原點:
case WM_PAINT:hdc = BeginPaint(hwnd,&ps);//設定視口原點SetViewportOrgEx(hdc,cxClient/2,cyClient/2,NULL);//畫一條直線MoveToEx(hdc,0,cyClient/2,NULL);LineTo(hdc,cxClient,cyClient/2);//繪製正弦曲線for(int i = 0; i< NUM;i++){//把x軸等分成1000份apt[i].x = i * cxClient / NUM;apt[i].y = (int) (cyClient / 2 * (1-sin(TWOPI * i /NUM)));LineTo(hdc,apt[i].x,apt[i].y);//Sleep(10);}EndPaint(hwnd,&ps);return 0;
可以預見到,將視口的原點設成了客戶區的中間,而我在畫圖時,第一條直線是從原點下面半個客戶區的範圍畫的,所以直線剛好看不到。而正弦曲線也只能看到一半。
通過更改視口的原點,還有一個重要的應用,就是做出字幕“滾動”的效果:
程式的其他部分見於:windows程式設計(6):基本畫圖
http://blog.csdn.net/thefutureisour/article/details/7568955
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){//字元的寬度,大寫字母寬度,字元高度static int cxChar, cxCaps, cyChar, iBegin;//視窗大小static int cxClient, cyClient ;//捲軸位置static int iVertPos,iHorzPos,iPaintBeg,iPaintEnd;HDC hdc;//該變數用於索引sysmets.h中定義的結構體數組sysmetrics[]的每個元素int i;//輸出文本的位置int x,y;//繪圖結構PAINTSTRUCT ps;//字串TCHAR szBuffer [10];//字型資訊結構TEXTMETRIC tm;switch(message){case WM_CREATE:SetTimer(hwnd,ID_TIMER,20,NULL);hdc = GetDC(hwnd);//取得內定系統字型的文字大小,存在放在tm裡GetTextMetrics (hdc, &tm);//平均字元寬cxChar = tm.tmAveCharWidth ;//大寫字母的平均寬度cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;//字元總高度:高度+行間距cyChar = tm.tmHeight + tm.tmExternalLeading ;ReleaseDC(hwnd,hdc);return 0;case WM_TIMER:iBegin++;InvalidateRect(hwnd,NULL,TRUE);case WM_SIZE:cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0; case WM_PAINT:hdc = BeginPaint (hwnd, &ps) ;//不斷地改變原點SetWindowOrgEx(hdc,0,iBegin,NULL);for(i = 0; i<NUMLINES;i++){TextOut(hdc,0,cyChar*i,sysmetrics[i].szLabel,lstrlen(sysmetrics[i].szLabel));TextOut(hdc,22 * cxChar,cyChar*i,sysmetrics[i].szDesc,lstrlen(sysmetrics[i].szDesc));SetTextAlign(hdc,TA_RIGHT | TA_TOP);TextOut(hdc,22*cxCaps + 40 * cxChar,cyChar * i,szBuffer,wsprintf(szBuffer,TEXT("%5d"),GetSystemMetrics(sysmetrics[i].Index)));SetTextAlign(hdc,TA_LEFT | TA_TOP);}EndPaint (hwnd, &ps) ;return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc (hwnd, message, wParam, lParam) ;}
“度量”映射方式
具體包含一下5種:
映像方式 |
邏輯單位 |
英寸 |
毫米 |
MM_LOENGLISH |
0.01 in. |
0.01 |
0.254 |
MM_LOMETRIC |
0.1 mm. |
0.00394 |
0.1 |
MM_HIENGLISH |
0.001 in. |
0.001 |
0.0254 |
MM_TWIPS |
1/1400 in. |
0.000694 |
0.0176 |
MM_HIMETRIC |
0.01 mm. |
0.000394 |
0.01 |
之所以成為“度量”,因為它們使用的是實際的尺寸。
通常我們感興趣的是視窗和視口的轉換因子。
在MM_LOMETRIC模式下,xViewExt/xWinExt表示每水平方向上每0.01英寸中的像素數,這是系統的一個值。在Win7系統中,控制台->顯示->設定自訂文字大小中,當選擇為100%時,每英寸有96個像素。所以比例為:96/100。可以看出,轉換因子是不變的。
有一點需要特別注意,就是這幾種模式的座標系相當於把笛卡爾座標系的原點搬到了左上方,意味著你看到的y軸都是負的。比如:
SetMapMode (hdc, MM_LOENGLISH) ; TextOut (hdc, 100, -100, "Hello", 5)
將把文字顯示在距離顯示地區左邊和上邊各一英寸的地方。
“自作主張的”的映射方式
剩下映射方式只有MM_ISOTROPIC和MM_ANISOTROPIC。它們可以改變視口到視窗的轉換因子。
MM_ISOTROPIC表示各向同性,表明水平和垂直方向的轉換因子是相同的。這意味著,如果你在客戶區上畫一個矩形,隨著客戶區的變大變小,矩形也會變大變小,而且矩形的長寬比是不變的。
MM_ANISOTROPIC表示各向異性,表明水平和垂直方向的轉換因子是不同的。我們下面具體討論一下這兩種方式:
MM_ISOTROPIC
當您剛開始將映像方式設定為MM_ISOTROPIC時,Windows使用與MM_LOMETRIC同樣的視窗和視連接埠範圍。(注意y軸是負的)
您可以用所期望的邏輯視窗的邏輯尺寸作為SetWindowExtEx的參數,用客戶區的實際寬和高作為SetViewportExtEx的參數。Windows在調整這些範圍時,必須讓邏輯視窗適應實際視窗,這就有可能導致客戶區的一段落到了邏輯視窗的外面。所以必須在呼叫SetViewportExtEx之前呼叫SetWindowExtEx。
MM_ANISOTROPIC
在MM_ISOTROPIC映像方式下設定視窗和視連接埠範圍時,Windows會調整範圍,以便兩條軸上的邏輯單位具有相同的實際尺度。在MM_ANISOTROPIC映射方式下,Windows不對您所設定的值進行調整,這就是說,MM_ANISOTROPIC不需要維持正確的縱橫比。
這裡舉一個例子:
#define LOGWIDE 4000
#define LOGHIGH 3000
case WM_PAINT:hdc = BeginPaint(hwnd,&ps);//設定映射模式SetMapMode(hdc,MM_ANISOTROPIC);//SetWindowExtEx(hdc,1,1,NULL);//SetViewportExtEx(hdc,1,-1,NULL);SetWindowExtEx(hdc,LOGWIDE,LOGHIGH,NULL);SetViewportExtEx(hdc,cxClient,-cyClient,NULL);SetViewportOrgEx(hdc,0,cyClient /2,NULL);//畫一條直線MoveToEx(hdc,0,0,NULL);LineTo(hdc,LOGWIDE,0);MoveToEx(hdc,0,0,NULL);//繪製正弦曲線//for(int i = 0; i< NUM;i++)for( i = 0; i< LOGWIDE;i++){//把x軸等分成1000份//apt[i].x = i * cxClient / NUM;apt[i].x = i;//apt[i].y = (int) (cyClient / 2 * (sin(TWOPI * i /NUM)));apt[i].y = (int) (LOGHIGH / 2 * sin(TWOPI * i /LOGWIDE));LineTo(hdc,apt[i].x,apt[i].y);//Sleep(10);}EndPaint(hwnd,&ps);return 0;
視窗範圍為(4000,,3000),而視口範圍(1,1).這是什麼意思呢?我們可以從兩個方面理解:
1.比例關係:4000:客戶區大小(比如800)意味著每5個邏輯單位映射為1個像素
2.在邏輯座標下選一個範圍(寬*高),裝置座標下也有一個範圍(客戶區的寬*高),視窗範圍裡的東西嚴格按比例映射到視口。這個理解方法也是我們通常使用的。
想到這裡,我覺得微軟設計的這個視窗是很好用的。舉一個例子,比如股市軟體。我們可以儲存每一天的資訊,然後當我們需要分析哪一段時間之後,我們可以選定一個視窗範圍,這個範圍內的資料是我們需要的那一段,然後把它展示出來。這時我不要考慮具體是怎麼展示的,系統會通過映射模式自動幫你把視窗的內容轉化到視口上顯示出來。