MINIGUI 开发指南---GDI
引言
GUI 系統的一個重要組成部分就是 GDI,即圖形設備接口(Graphics Device Interface)。通過 GDI,GUI 程序就可以在計算機屏幕上,或者其他的顯示設備上進行圖形輸出,包括基本繪圖和文本輸出。本文將詳細描述 MiniGUI 中的 GDI 函數,并舉例說明重要函數的用法。其中包括:DC 的概念、獲取和釋放;矩形操作和剪切域操作;基本繪圖函數;位圖操作函數;邏輯字體操作函數等。
圖形設備上下文
在 MiniGUI 中,采用了在 Windows 和 X Window 中普遍采用的圖形設備概念。每個圖形設備定義了計算機顯示屏幕上的一個矩形輸出區域。在調用圖形輸出函數時,均要求指定經初始化的圖形設備上下文(Device Context,DC),也稱作"設備環境"。從程序員的角度看,一個經過初始化的圖形設備上下文確定了其后進行圖形輸出的一些基本屬性,并一直保持這些屬性,直到被改變為止。這些屬性包括:輸出的線條顏色、填充顏色、字體顏色、字體形狀等等。而從 GUI 系統角度來講,一個圖形設備上下文所代表的含義就要復雜得多,它起碼應該包含如下內容:
所以,從程序員的角度看來,他所關心的僅僅是設備上下文本身的一小部分東西。
2.1 設備上下文的獲取和釋放
在 MiniGUI 中,所有繪圖相關的函數均需要有一個設備上下文。設備上下文可通過 GetClientDC 和 ReleaseDC 獲取和釋放。由 GetDC 所獲取的設備上下文是針對整個窗口的,而 GetClientDC 所獲取的設備上下文是針對窗口客戶區,也就是說,前一個函數獲得的設備上下文,其坐標原點位于窗口左上角,輸出被限定在窗口范圍之內;后一個函數獲得的設備上下文,其坐標原點位于窗口客戶區左上角,輸出被限定在窗口客戶區范圍之內。下面是這三個函數的原型說明(include/gdi.h):
| 398 HDC GUIAPI GetDC (HWND hwnd);399 HDC GUIAPI GetClientDC (HWND hwnd);400 void GUIAPI ReleaseDC (HDC hdc); |
GetDC 和 GetClientDC 是從系統預留的若干個 DC 當中獲得一個目前尚未使用的設備上下文。所以,應該注意如下兩點:
為了方便程序編寫,提高繪圖效率,MiniGUI 還提供了建立私有設備上下文的函數,所建立的設備上下文在整個窗口生存期內有效,從而免除了獲取和釋放的過程。這些函數的原型如下:
| 403 HDC GUIAPI CreatePrivateDC (HWND hwnd);404 HDC GUIAPI CreatePrivateClientDC (HWND hwnd);405 HDC GUIAPI GetPrivateClientDC (HWND hwnd);406 void GUIAPI DeletePrivateDC (HDC hdc); |
在建立主窗口時,如果主窗口的擴展風格中指定了 WS_EX_USEPRIVATEDC 風格,則 CreateMainWindow 函數會自動為該窗口的客戶區建立私有設備上下文。通過 GetPrivateClientDC 函數,可以獲得該設備上下文。對控件而言,如果控件類具有 CS_OWNDC 屬性,則所有屬于該控件類的控件將自動建立私有設備上下文。DeletePrivateDC 函數用來刪除私有設備上下文。對上述兩種情況,系統將在銷毀窗口時自動調用 DeletePrivateDC 函數。
另外一個獲取和釋放設備上下文的方法是通過 BeginPaint 和 EndPaint 函數。這兩個函數只能在處理 MSG_PAINT 的消息中調用。MiniGUI 在 BeginPaint 函數中通過 GetClientDC 獲取客戶區設備上下文,然后將窗口當前的無效區域選擇到窗口的剪切區域中;而 EndPaint 函數則清空窗口的無效區域,并釋放設備上下文。這兩個函數的原型如下(include/window.h):
| 623 HDC GUIAPI BeginPaint(HWND hWnd);624 void GUIAPI EndPaint(HWND hWnd, HDC hdc); |
因為 BeginPaint 函數將窗口的無效區域選擇到了設備上下文中,所以,可以通過一些必要的優化來提高 MSG_PAINT 消息的處理效率。比如,某個程序要在窗口客戶區中填充若干矩形,就可以在 MSG_PAINT 函數中如下處理:
| MSG_PAINT: {HDC hdc = BeginPaint (hWnd);for (j = 0; j < 10; j ++) {if (RectVisible (hdc, rcs + j)) {FillBox (hdc, rcs[j].left, rcs[j].top, rcs [j].right, rcs [j].bottom);}}EndPaint (hWnd, hdc);return 0; } |
這樣可以避免不必要的重繪操作,從而提高繪圖效率。?
2.2 系統內存中的設備上下文
MiniGUI 也提供了內存設備上下文的創建和銷毀函數。利用內存設備上下文,可以在系統內存中建立一個類似顯示內存的區域,然后在該區域中進行繪圖操作,結束后再復制到顯示內存中。這種繪圖方法有許多好處,比如速度很快,減少直接操作顯存造成的閃爍現象等等。不過,目前 MiniGUI 中只能建立和顯示內存,也就是物理設備上下文一樣的內存設備上下文。用來建立和銷毀內存設備上下文的函數原型如下(include/gdi.h):
| 401 HDC GUIAPI CreateCompatibleDC (HDC hdc);402 void GUIAPI DeleteCompatibleDC (HDC hdc); |
2.3 屏幕設備上下文
MiniGUI 在啟動之后,就建立了一個全局的屏幕設備上下文。該 DC 是針對整個屏幕的,并且沒有任何預先定義的剪切域。在某些應用程序中,可以直接使用該設備上下文進行繪圖,將大大提高繪圖效率。在 MiniGUI 中,屏幕設備上下文用 HDC_SCREEN 標識,不需要進行任何獲取和釋放操作。
2.4 映射模式
一個設備上下文被初始化之后,其坐標系原點通常是輸出矩形的左上角,而 x 軸水平向左,y 軸垂直向下,并以象素為單位。這種坐標的映射模式標識為 MM_TEXT。MiniGUI 提供了一套函數,可以改變這種映射方式,包括對默認坐標系進行偏移、縮放等操作。這些函數的原型如下(include/gdi.h):
| 453 int GUIAPI GetMapMode (HDC hdc);454 void GUIAPI GetViewportExt (HDC hdc, POINT* pPt);455 void GUIAPI GetViewportOrg (HDC hdc, POINT* pPt);456 void GUIAPI GetWindowExt (HDC hdc, POINT* pPt);457 void GUIAPI GetWindowOrg (HDC hdc, POINT* pPt);458 void GUIAPI SetMapMode (HDC hdc, int mapmode);459 void GUIAPI SetViewportExt (HDC hdc, POINT* pPt);460 void GUIAPI SetViewportOrg (HDC hdc, POINT* pPt);461 void GUIAPI SetWindowExt (HDC hdc, POINT* pPt);462 void GUIAPI SetWindowOrg (HDC hdc, POINT* pPt); |
GetMapMode 函數返回當前的映射模式,若不是 MM_TEXT 模式,則返回MM_ANISOTROPIC。SetMapMode 函數設置映射模式,MiniGUI 目前只支持兩種映射模式,即MM_ANISOTROPIC 和 MM_TEXT。Get 函數組用來返回映射模式信息,包括偏移量、縮放比例等等,而 Set 函數組用來設置相應的映射信息。
通常情況下,MiniGUI 的 GDI 函數所指定的坐標參數稱為"邏輯坐標",在繪制之前,首先要轉化成"設備坐標"。當使用 MM_TEXT 映射模式時,邏輯坐標和設備坐標是等價的。LPtoDP 函數用來完成邏輯坐標到設備坐標的轉換,DPtoLP 函數用來完成從設備坐標到邏輯坐標的轉換。邏輯坐標和設備坐標的關系可從 LPtoDP 函數中看到(src/gdi/coor.c):
| 61 void GUIAPI LPtoDP(HDC hdc, POINT* pPt)62 {63 PDC pdc;64 65 pdc = dc_HDC2PDC(hdc);66 67 if (pdc->mapmode != MM_TEXT) {68 pPt->x = (pPt->x - pdc->WindowOrig.x)69 * pdc->ViewExtent.x / pdc->WindowExtent.x70 + pdc->ViewOrig.x;71 72 pPt->y = (pPt->y - pdc->WindowOrig.y)73 * pdc->ViewExtent.y / pdc->WindowExtent.y74 + pdc->ViewOrig.y;75 }76 }77 |
另外,LPtoSP 函數和 SPtoLP 函數完成邏輯坐標和屏幕坐標之間的轉換。?
矩形操作和區域操作
3.1 矩形操作
在 MiniGUI 中,矩形是如下定義的(include/common.h):
| 120 typedef struct tagRECT121 {122 int left;123 int top;124 int right;125 int bottom;126 } RECT;127 typedef RECT* PRECT;128 typedef RECT* LPRECT; |
簡而言之,矩形就是用來表示屏幕上一個矩形區域的數據結構,定義了矩形左上角的 x, y 坐標(left 和 top)以及右下角的 x, y 坐標(right 和 bottom)。需要注意的是,MiniGUI 中的矩形,其右側的邊和下面的邊是不屬于該矩形的。例如,要表示屏幕上的一條掃描線,應該用?RECT rc = {x, y, x + w + 1, y + 1};?表示。其中 x 是掃描線的起點,y 是掃描線的垂直位置,w 是掃描線寬度。
MiniGUI 提供了一組函數,可對 RECT 對象進行操作:
- SetRect 對 RECT 對象的各個分量進行賦值;?
- SetRectEmpty 將 RECT 對象設置為空。MiniGUI 中的空矩形定義為高度或寬度為零的矩形;?
- IsRectEmpty 判斷給定 RECT 對象是否為空。?
- NormalizeRect 對給定矩形進行正規化處理。MiniGUI 中的矩形,應該滿足(right > left 并且 bottom > top)的條件。滿足這一條件的矩形又稱"正規化矩形",該函數可以對任意矩形進行正規化處理。?
- CopyRect 復制矩形;?
- EqualRect 判斷兩個 RECT 對象是否相等,即兩個 RECT 對象的各個分量相等;?
- IntersectRect 該函數求兩個 RECT 對象之交集。若兩個矩形根本不相交,則函數返回 FALSE,且結果矩形未定義;否則返回交矩形。
- DoesIntersec 該函數僅僅判斷兩個矩形是否相交。?
- IsCovered 該函數判斷 RECT 對象 A 是否全部覆蓋 RECT 對象 B,即 RECT B 是 RECT A 的真子集。?
- UnionRect 該函數求兩個矩形之并。如果兩個矩形根本無法相并,則返回 FALSE。兩個相并之后的矩形,其中所包含的任意點,應該屬于兩個相并矩形之一。?
- GetBoundRect 該函數求兩個矩形的外包最小矩形。?
- SubstractRect 該函數從一個矩形中減去另外一個矩形。注意,兩個矩形相減的結果可能生成 4 個不相交的矩形。該函數將返回結果矩形的個數以及差矩形。詳細信息可參見"MiniGUI 體系結構之二?D?D多窗口管理和控件及控件類"一文。?
- OffsetRect 該函數對給定的 RECT 對象進行平移處理。?
- InflateRect 該函數對給定的 RECT 對象進行膨脹處理。注意膨脹之后的矩形寬度和高度是給定膨脹值的兩倍。?
- InflateRectToPt 該函數將給定的 RECT 對象膨脹到指定的點。?
- PtInRect 該函數判斷給定的點是否位于指定的 RECT 對象中。
3.2 區域操作
在 MiniGUI 中,區域定義為互不相交矩形的集合,在內部用鏈表形式表示。MiniGUI 的區域可以用來表示窗口的剪切域、無效區域、可見區域等等。在 MiniGUI 中,區域和剪切域的定義是一樣的,剪切域定義如下(include/gdi.h):
| 76 // Clip Rect77 typedef struct tagCLIPRECT78 {79 RECT rc;80 struct tagCLIPRECT* next;81 }CLIPRECT;82 typedef CLIPRECT* PCLIPRECT;8384 // Clip Region85 typedef struct tagCLIPRGN86 {87 RECT rcBound; // bound rect of clip region88 PCLIPRECT head; // clip rect list head89 PCLIPRECT tail; // clip rect list tail90 PBLOCKHEAP heap; // heap of clip rect91 } CLIPRGN;92 typedef CLIPRGN* PCLIPRGN; |
每個剪切域對象有一個 BLOCKHEAP 成員。該成員是剪切域分配 RECT 對象的私有堆。在使用一個剪切域對象之前,首先應該建立一個 BLOCKHEAP 對象,并對剪切域對象進行初始化。如下所示:
| static BLOCKHEAP sg_MyFreeClipRectList; ...CLIPRGN my_regionInitFreeClipRectList (&sg_MyFreeClipRectList, 20);InitClipRgn (&my_regioni, &sg_MyFreeClipRectList); |
在實際使用當中,多個剪切域可以共享同一個 BLOCKHEAP 對象。?
在初始化剪切域對象之后,可以對剪切域進行如下操作:
- SetClipRgn 該函數將剪切域設置為僅包含一個矩形的剪切域;?
- ClipRgnCopy 該函數復制剪切域;?
- ClipRgnIntersect 該函數求兩個剪切域的交集;?
- GetClipRgnBoundRect 該函數求剪切域的外包最小矩形;?
- IsEmptyClipRgn 該函數判斷剪切域是否為空,即是否包含剪切矩形;?
- EmptyClipRgn 該函數釋放剪切域中的剪切矩形,并清空剪切域;?
- AddClipRect 該函數將一個剪切矩形追加到剪切域中。注意該操作并不判斷該剪切域是否和剪切矩形相交。?
- IntersectClipRect 該函數求剪切區域和給定矩形相交的剪切區域。?
- SubtractClipRect 該函數從剪切區域中減去指定的矩形。
總結
以上是生活随笔為你收集整理的MINIGUI 开发指南---GDI的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MiniGUI细节处理(转)
- 下一篇: MiniGUI.cfg 文件详解