本文讲述 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.x 70 + pdc->ViewOrig.x; 71 72 pPt->y = (pPt->y - pdc->WindowOrig.y) 73 * pdc->ViewExtent.y / pdc->WindowExtent.y 74 + pdc->ViewOrig.y; 75 } 76 } 77 |
矩形操作和区域操作
3.1 矩形操作
在 MiniGUI 中,矩形是如下定义的(include/common.h):
120 typedef struct tagRECT 121 { 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 对象进行操作:
3.2 区域操作
在 MiniGUI 中,区域定义为互不相交矩形的集合,在内部用链表形式表示。MiniGUI 的区域可以用来表示窗口的剪切域、无效区域、可见区域等等。在 MiniGUI 中,区域和剪切域的定义是一样的,剪切域定义如下(include/gdi.h):
76 // Clip Rect 77 typedef struct tagCLIPRECT 78 { 79 RECT rc; 80 struct tagCLIPRECT* next; 81 }CLIPRECT; 82 typedef CLIPRECT* PCLIPRECT; 83 84 // Clip Region 85 typedef struct tagCLIPRGN 86 { 87 RECT rcBound; // bound rect of clip region 88 PCLIPRECT head; // clip rect list head 89 PCLIPRECT tail; // clip rect list tail 90 PBLOCKHEAP heap; // heap of clip rect 91 } CLIPRGN; 92 typedef CLIPRGN* PCLIPRGN; |
每个剪切域对象有一个 BLOCKHEAP 成员。该成员是剪切域分配 RECT 对象的私有堆。在使用一个剪切域对象之前,首先应该建立一个 BLOCKHEAP 对象,并对剪切域对象进行初始化。如下所示:
static BLOCKHEAP sg_MyFreeClipRectList; ... CLIPRGN my_region InitFreeClipRectList (&sg_MyFreeClipRectList, 20); InitClipRgn (&my_regioni, &sg_MyFreeClipRectList); |
在初始化剪切域对象之后,可以对剪切域进行如下操作: