Windows Programming/GDI與繪圖
設備上下文
編輯設備上下文DC是要繪圖的環境設備的資料結構。每一個窗口都對應一個DC。可以把窗口的DC描述為對應於一塊顯存。
系統不允許直接向窗口DC加載一份位圖。必須用函數CreateCompatibleBitmap在內存中創建窗口的一個兼容DC(此時是單色的1*1像素大小),用函數CreateCompatibleBitmap或者LoadImage加載一個位圖,用函數SelectObject把位圖加載到兼容DC中(此時就有了繪圖的點陣),再用函數BitBlt把兼容DC的一部分拷貝到窗口DC。可以通過調用GetDeviceCaps函數來確定一個設備是否支持光柵操作。當不再需要內存設備上下文環境時,可調用DeleteDc函數刪除它。
GDI 坐標變換
編輯Win32 API使用四種坐標空間:[1]
- 世界坐標系空間:允許平移、旋轉、尺度、剪切、翻轉等變化。使用SetWorldTransform函數通知DC將使用世界坐標系。此後的邏輯坐標系空間是世界坐標系空間。
- 頁面坐標空間,是通常的邏輯坐標系。
- 設備坐標空間:只允許到物理設備坐標空間的平移變換。總是使用像素作為坐標值,x、y軸的零點總是在左上角。
- 物理設備坐標空間:通常指程序窗口的客戶區,但也可以是整個桌面、應用程式的整個窗口(包括框架、標題欄和菜單欄)、印表機的一頁、繪圖儀的一頁紙。
- 客戶區域坐標:左上角為(0,0)。通過GetDC()函數獲取。
- 屏幕坐標,包括整個屏幕,屏幕的左上角為(0,0)屏幕坐標。通過GetDC()函數獲取。對於非子窗口,WM_MOVE消息、CreateWindow函數和MoveWindow函數。以及GetMessage、GetCursorPos、GetWindowRect、WindowFromPoint和SetBrushOrg等函數。ClientToScreen和ScreenToClient函數可在客戶區域坐標與屏幕區域坐標之間轉換。
- (應用程式)框架窗口坐標,包括應用程式的標題條、菜單、滾動條和窗口框,窗口的左上角為(0,0)。使用GetWindowDC()函數得到的窗口設備環境,可以將邏輯單位轉換成窗口坐標。
設備空間用於提供一個設備無關的坐標系。設備空間到物理空間的轉換隻限於平移,並由Windows的窗口管理部分控制,這種轉換的唯一用途是確保設備空間的原點被映射到物理設備上的適當點上。沒有函數能設置這種轉換,也沒有函數可以獲取有關數據。
除了少數特例,GDI繪圖API函數使用邏輯坐標。windows對所有的消息(如WM_SIZE,WM_MOUSEMOVE,WM_LBUTTONDOWN, WM_LBUTTONUP),所有的非GDI函數和少數GDI函數(如GetDeviceCaps函數)永遠使用設備坐標。滑鼠的光標位置用設備坐標表示。可以認為MFC的CDC的所有成員函數都以邏輯坐標作為其參數;CWnd的成員函數都以設備坐標作為其參數;所有選中測試操作(如CRect::PtInRect)都應考慮設備坐標。邏輯坐標和設備坐標的轉換由映射模式決定。映射模式被儲存在設備環境(DC)中。GetMapMode函數用於從設備環境得到當前的映射模式,SetMapMode函數用於設置設備環境的映射模式。有8種映射方式。預設映射模式MM_TEXT的得名,是因為軸的方向與讀文本的方向一致。
邏輯坐標系與設備坐標系變換時,需要注意「原點」(origin point)並不是通常所指,而是一個坐標系中一點,用於同另一個坐標系的「原點」重合。因此,坐標系的x、y坐標都為0的點,本文稱作「零點」以示區別。邏輯坐標所在的坐標系稱為"窗口"(Windowport),將設備坐標所在的坐標系稱為"視口"(Viewport)。邏輯坐標系原點(xWinOrg,yWinOrg)總被映射為設備坐標系原點(xViewOrg,yViewOrg)。API函數SetViewportOrg和SetWindowOrg 用來設置視口和窗口的原點。
API函數SetWindowExt和SetViewportExt 用於改變窗口和視口的範圍。如果參數為負值表示相應的坐標軸反向。範圍本身的值並不重要,但兩個範圍的比值用於坐標變換。
API函數DPtoLP和LPtoDP在設備坐標及邏輯坐標之間互相轉換。
如果未做任何坐標系變換,邏輯坐標和設備坐標在預設模式(MM_TEXT)下,如果移動了客戶區的垂直或水平滾動條,這兩種坐標就不簡單對應了。
示例
編輯void CMFCApplication1View::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_LOMETRIC); // 此后逻辑坐标系的y轴为垂直向上
pDC->SetViewportOrg(100,100); // 设备坐标的原点设在设备空间的(100,100), 该原点总是与逻辑坐标系原点重合
pDC->Rectangle(0, 0, 40, 40); // 在逻辑坐标的第一象限画出一个空心正方形
pDC->SelectStockObject(GRAY_BRUSH);
pDC->Rectangle(-10, -10, 10, 10); // 围绕逻辑坐标零点画出一个小型灰色正方形
pDC->TextOutW(50,0,CString("Zero Point 1")); // 目前,重合的原点在此,同时也是逻辑坐标的零点
pDC->SetWindowOrg(200, 200); // 逻辑坐标系的原点设在逻辑坐标(200,200),然后该原点与此时的设备坐标的原点重合
pDC->SelectStockObject(BLACK_BRUSH);
pDC->Rectangle(-10, -10, 10, 10); // 围绕此时的逻辑坐标零点画出一个小型黑色正方形
pDC->Rectangle(0, 0, 40, 40); // 在此时的逻辑坐标的第一象限画出一个黑色正方形
pDC->TextOutW(50, 0, CString("Zero Point 2")); //此时的逻辑坐标零点
}
- GetWindowRect() 得到的是在屏幕坐標系下的RECT(即以屏幕左上角為原點)
- GetClientRect() 得到的是在客戶區坐標系下的窗口客戶區的RECT。因此返回值的左上角永遠為(0,0),只具有描述客戶區大小的意義
- ScreenToClient() 就是把屏幕坐標系下的RECT坐標轉換為指定窗口的客戶區坐標系下的RECT坐標。
繪圖時機
編輯- Invalidate把整個客戶區標為無效,屏幕不一定馬上更新。
- InvalidateRect()的功能與Invalidate基本一樣,指定某個矩形區域無效
- UpdateWindow繞過了消息隊列,直接按照WM_PAINT消息調用窗口過程,其無效區範圍就是消息隊列中WM_PAINT消息已有的無效區。
- RedrawWindow比UpdateWindow功能更為豐富。