本文讲述 MiniGUI 中 GDI 函数及其使用。主要包括:设备上下文的概念、获取和释放;矩形操作和区域操作;基本绘图函数;位图操作函数;逻辑字体操作函数等。
引言
GUI 系统的一个重要组成部分就是 GDI,即图形设备接口(Graphics Device Interface)。通过 GDI,GUI 程序就可以在计算机屏幕上,或者其他的显示设备上进行图形输出,包括基本绘图和文本输出。本文将详细描述 MiniGUI 中的 GDI 函数,并举例说明重要函数的用法。其中包括:DC 的概念、获取和释放;矩形操作和剪切域操作;基本绘图函数;位图操作函数;逻辑字体操作函数等。
图形设备上下文
在 MiniGUI 中,采用了在 Windows 和 X Window 中普遍采用的图形设备概念。每个图形设备定义了计算机显示屏幕上的一个矩形输出区域。在调用图形输出函数时,均要求指定经初始化的图形设备上下文(Device Context,DC),也称作"设备环境"。从程序员的角度看,一个经过初始化的图形设备上下文确定了其后进行图形输出的一些基本属性,并一直保持这些属性,直到被改变为止。这些属性包括:输出的线条颜色、填充颜色、字体颜色、字体形状等等。而从 GUI 系统角度来讲,一个图形设备上下文所代表的含义就要复杂得多,它起码应该包含如下内容:
- 该设备上下文本所在设备信息(显示模式、色彩深度、显存布局等等);
- 该设备上下文所代表的窗口以及该窗口被其他窗口剪切的信息(在 MiniGUI 中,称作"全局剪切域");
- 该设备上下文的基本操作函数(点、直线、多边形、填充、块操作等),及其上下文信息;
- 由程序设定的局部信息(绘图属性、映射关系和局部剪切域等)。
所以,从程序员的角度看来,他所关心的仅仅是设备上下文本身的一小部分东西。
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 当中获得一个目前尚未使用的设备上下文。所以,应该注意如下两点:
- 在使用完成一个由 GetDC 返回的设备上下文之后,应该尽快调用 ReleaseDC 释放。
- 避免同时使用多个设备上下文,并避免在递归函数中调用 GetDC 和 GetClientDC。
为了方便程序编写,提高绘图效率,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 |
另外,LPtoSP 函数和 SPtoLP 函数完成逻辑坐标和屏幕坐标之间的转换。
矩形操作和区域操作
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 对象进行操作:
- 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 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); |
在实际使用当中,多个剪切域可以共享同一个 BLOCKHEAP 对象。
在初始化剪切域对象之后,可以对剪切域进行如下操作:
- SetClipRgn 该函数将剪切域设置为仅包含一个矩形的剪切域;
- ClipRgnCopy 该函数复制剪切域;
- ClipRgnIntersect 该函数求两个剪切域的交集;
- GetClipRgnBoundRect 该函数求剪切域的外包最小矩形;
- IsEmptyClipRgn 该函数判断剪切域是否为空,即是否包含剪切矩形;
- EmptyClipRgn 该函数释放剪切域中的剪切矩形,并清空剪切域;
- AddClipRect 该函数将一个剪切矩形追加到剪切域中。注意该操作并不判断该剪切域是否和剪切矩形相交。
- IntersectClipRect 该函数求剪切区域和给定矩形相交的剪切区域。
- SubtractClipRect 该函数从剪切区域中减去指定的矩形。