📄 基于 linux 和 minigui 的嵌入式系统软件开发指南(七).htm
字号:
<P>和垂直单调多边形生成器一样,该函数生成的是填充多边形的每一条水平扫描线:x1 是水平线的起始X坐标;x2 是水平线的终止 X 坐标;y
是水平线的 Y 坐标值。</P>
<P><A id=27 name=27><SPAN class=atitle3>2.7 填注生成器</SPAN></A></P>
<P>填注(flood filling)生成器比较复杂。这个函数在 MiniGUI 内部用于 FloodFill 函数。我们知道,FloodFill
函数从给定的起始位置开始,以给定的颜色向四面八方填充某个区域(像水一样蔓延,因此叫 Flood
Filling),一直到遇到与给定起始位置的象素值不同的点为止。因此,在这一过程中,我们需要两个回调函数,一个回调函数用来判断蔓延过程中遇到的点的象素值是否和起始点相同,另外一个回调函数用来生成填充该区域的水平扫描线。在进行绘图时,该函数比较的是象素值,但实际上,该函数也可以比较任何其他值,从而完成特有的蔓延动作。这就是将填注生成器单独出来的初衷。MiniGUI
如下定义填注生成器:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>/* General Flood Filling generator */
typedef BOOL (* CB_EQUAL_PIXEL) (void* context, int x, int y);
typedef void (* CB_FLOOD_FILL) (void* context, int x1, int x2, int y);
BOOL GUIAPI FloodFillGenerator (void* context, const RECT* src_rc, int x, int y,
CB_EQUAL_PIXEL cb_equal_pixel, CB_FLOOD_FILL cb_flood_fill);
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>cb_equal_pixel 被调用,以便判断目标点的象素值是否和起始点一样,起始点的象素值可以通过 context
来传递。cb_flood_fill 函数用来填充一条扫描线,传递的是水平扫描线的端点,即(x1, y) 和 (x2, y)。</P>
<P><A id=28 name=28><SPAN class=atitle3>2.8 曲线和填充生成器的用法</SPAN></A></P>
<P>曲线和填充生成器的用法非常简单。为了对曲线和填充生成器有个更好的了解,我们首先看 MiniGUI 内部是如何使用曲线和填充生成器的。</P>
<P>下面的程序段来自 MiniGUI 的 FloodFill 函数(src/newgdi/flood.c):</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>static void _flood_fill_draw_hline (void* context, int x1, int x2, int y)
{
PDC pdc = (PDC)context;
RECT rcOutput = {MIN (x1, x2), y, MAX (x1, x2) + 1, y + 1};
ENTER_DRAWING (pdc, rcOutput);
_dc_draw_hline_clip (context, x1, x2, y);
LEAVE_DRAWING (pdc, rcOutput);
}
static BOOL equal_pixel (void* context, int x, int y)
{
gal_pixel pixel = _dc_get_pixel_cursor ((PDC)context, x, y);
return ((PDC)context)->skip_pixel == pixel;
}
/* FloodFill
* Fills an enclosed area (starting at point x, y).
*/
BOOL GUIAPI FloodFill (HDC hdc, int x, int y)
{
PDC pdc;
BOOL ret = TRUE;
if (!(pdc = check_ecrgn (hdc)))
return TRUE;
/* hide cursor tempororily */
ShowCursor (FALSE);
coor_LP2SP (pdc, &x, &y);
pdc->cur_pixel = pdc->brushcolor;
pdc->cur_ban = NULL;
pdc->skip_pixel = _dc_get_pixel_cursor (pdc, x, y);
/* does the start point have a equal value? */
if (pdc->skip_pixel == pdc->brushcolor)
goto equal_pixel;
ret = FloodFillGenerator (pdc, &pdc->DevRC, x, y, equal_pixel, _flood_fill_draw_hline);
equal_pixel:
UNLOCK_GCRINFO (pdc);
/* Show cursor */
ShowCursor (TRUE);
return ret;
}
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>该函数在经过一些必要的初始化工作之后,调用 FloodFillGenerator 函数,并传递了上下文 pdc (pdc 是 MiniGUI
内部表示 DC 的数据结构)和两个回调函数地址:equal_pixel 和 _flood_fill_draw_hline
函数。在这之前,该函数获得了起始点的象素值,并保存在了pdc->skip_pixel 当中。equal_pixel
函数获得给定点的象素值,然后返回与 pdc->skip_pixel 相比较之后的值;_flood_fill_draw_hline
函数调用内部函数进行水平线的绘制。</P>
<P>读者可以看到,这种简单的生成器实现方式,能够大大降低代码复杂度,提高代码的重用能力。有兴趣的读者可以比较 MiniGUI 新老 GDI 接口的
LineTo 函数实现,相信能够得出一样的结论。</P>
<P>当然设计生成器的目的主要还是为方便用户使用。比如,你可以利用 MiniGUI
内部的曲线生成器完成自己的工作。下面的示例假定你使用圆生成器绘制一个线宽为 4 象素的圆:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>static void draw_circle_pixel (void* context, int x1, int x2, int y)
{
HDC hdc = (HDC) context;
/* 以圆上的每个点为圆心,填充半径为 2 的圆。*/
FillCircle (hdc, x1, y, 2);
FillCircle (hdc, x2, y, 2);
}
void DrawMyCircle (HDC hdc, int x, int y, int r, gal_pixel pixel)
{
gal_pixel old_brush;
old_bursh = SetBrushColor (hdc, pixle);
/* 调用圆生成器 */
CircleGenerator ((void*)hdc, x, y, r, draw_circle_pixel);
/* 恢复旧的画刷颜色 */
SetBrushColor (hdc, old_brush);
}
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>从上面的例子可以看出,曲线和填充生成器的用法极其简单,而且结构清晰明了。读者在自己的开发过程中,也可以学习这种方法。</P>
<P><A id=3 name=3><SPAN class=atitle2>3 绘制复杂曲线</SPAN></A></P>
<P>基于 2 中描述的曲线生成器,MiniGUI 提供了如下基本的曲线绘制函数:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>void GUIAPI MoveTo (HDC hdc, int x, int y);
void GUIAPI LineTo (HDC hdc, int x, int y);
void GUIAPI Rectangle (HDC hdc, int x0, int y0, int x1, int y1);
void GUIAPI PollyLineTo (HDC hdc, const POINT* pts, int vertices);
void GUIAPI SplineTo (HDC hdc, const POINT* pts);
void GUIAPI Circle (HDC hdc, int sx, int sy, int r);
void GUIAPI Ellipse (HDC hdc, int sx, int sy, int rx, int ry);
void GUIAPI Arc (HDC hdc, int sx, int sy, int r, fixed ang1, fixed ang2);
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<UL class=noindent>
<LI>MoveTo 将当前画笔的起始点移动到给定点(x, y),以逻辑坐标指定。
<LI>LineTo 从当前画笔点画直线到给定点(x, y),以逻辑坐标指定。
<LI>Rectangle 函数画顶点为(x0, y0)和(x1, y0)的矩形。
<LI>PollyLineTo 函数利用 LineTo 函数画折线。pts 指定了折线的各个端点,vertices 指定了折线端点个数。
<LI>SplineTo 函数利用 LineTo 函数画三次样条曲线。需要注意的是,必须传递四个点才能惟一确定一条样条曲线,也就是说,pts
是一个指向包含 4 个 POINT 结构数组的指针。
<LI>Circle 函数绘制圆,圆心为 (sx, sy),半径为 r,以逻辑坐标指定。
<LI>Ellipse 函数绘制椭圆,椭圆心为(sx, sy),X 轴半径为 rx,Y 轴半径为 ry。
<LI>Arc 函数绘制圆弧,(sx, sy) 指定了圆心,r 指定半径,ang1 和 ang2
指定圆弧的起始弧度和终止弧度。需要注意的是,ang1 和 ang2 是以定点数形式指定的。 </LI></UL>
<P>作为示例,我们看 Circle 和 Ellipse 函数的用法。假定给定了两个点,pts[0] 和 pts[1],其中 pts[0]
是圆心或者椭圆心,而 pts[1] 是圆或者椭圆外切矩形的一个顶点。下面的程序段绘制由这两个点给定的圆或者椭圆:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE> int rx = ABS (pts[1].x - pts[0].x);
int ry = ABS (pts[1].y - pts[0].y);
if (rx == ry)
Circle (hdc, pts[0].x, pts[0].y, rx);
else
Ellipse (hdc, pts[0].x, pts[0].y, rx, ry);
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P><A id=4 name=4><SPAN class=atitle2>4 封闭曲线填充</SPAN></A></P>
<P>MiniGUI 目前提供了如下的封闭曲线填充函数:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>void GUIAPI FillBox (HDC hdc, int x, int y, int w, int h);
void GUIAPI FillCircle (HDC hdc, int sx, int sy, int r);
void GUIAPI FillEllipse (HDC hdc, int sx, int sy, int rx, int ry);
void GUIAPI FillSector (HDC hdc, int sx, int sy, int r, int ang1, int ang2);
BOOL GUIAPI FillPolygon (HDC hdc, const POINT* pts, int vertices);
BOOL GUIAPI FloodFill (HDC hdc, int x, int y);
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<UL class=noindent>
<LI>FillBox 函数填充指定的矩形。该矩形左上角顶点为(x, y),宽度为 w,高度为 h,以逻辑坐标指定。
<LI>FillCircle 函数填充指定的圆。圆心为(sx, xy),半径为 r,以逻辑坐标指定。
<LI>FillEllips 函数填充指定的椭圆。椭圆心为(sx, sy),X 轴半径为 rx,Y 轴半径为 ry。
<LI>FillSector 函数填充由圆弧和两条半径形成的扇形。圆心为(x, y),半径为 r,起始弧度为 ang1,终止弧度为 ang2。
<LI>FillPolygon 函数填充多边形。pts 表示多边形各个顶点,vertices 表示多边形顶点个数。
<LI>FloodFill 从指定点(x, y)开始填注。 </LI></UL>
<P>需要注意的是,所有填充函数使用当前画刷属性(颜色),并且受当前光栅操作的影响。</P>
<P>下面的例子说明了如何使用 FillCircle 和 FillEllipse 函数填充圆或者椭圆。假定给定了两个点,pts[0] 和
pts[1],其中 pts[0] 是圆心或者椭圆心,而 pts[1] 是圆或者椭圆外切矩形的一个顶点。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE> int rx = ABS (pts[1].x - pts[0].x);
int ry = ABS (pts[1].y - pts[0].y);
if (rx == ry)
FillCircle (hdc, pts[0].x, pts[0].y, rx);
else
FillEllipse (hdc, pts[0].x, pts[0].y, rx, ry);
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P><A id=5 name=5><SPAN class=atitle2>5 建立复杂区域</SPAN></A></P>
<P>除了利用填充生成器进行填充绘制以外,我们还可以使用填充生成器建立由封闭曲线包围的复杂区域。我们知道,MiniGUI
当中的区域是由互不相交的矩形组成的,并且满足 x-y-banned
的分布规则。利用上述的多边形或者封闭曲线生成器,可以将每条扫描线看成是组成区域的高度为 1
的一个矩形,这样,我们可以利用这些生成器建立复杂区域。MiniGUI 利用现有的封闭曲线生成器,实现了如下的复杂区域生成函数:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>BOOL GUIAPI InitCircleRegion (PCLIPRGN dst, int x, int y, int r);
BOOL GUIAPI InitEllipseRegion (PCLIPRGN dst, int x, int y, int rx, int ry);
BOOL GUIAPI InitPolygonRegion (PCLIPRGN dst, const POINT* pts, int vertices);
BOOL GUIAPI InitSectorRegion (PCLIPRGN dst, const POINT* pts, int vertices);
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>利用这些函数,我们可以将某个区域分别初始化为圆、椭圆、多边形和扇形区域。然后,可以利用这些区域进行点击测试(PtInRegion 和
RectInRegion),或者选择到 DC 当中作为剪切域,从而获得特殊显示效果。</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -