本节讲述如何填充由线条构建的封闭区域。当我们初始化一个窗口类时,往往已经指定了窗口的背景色画刷(WHITE_BRUSH),即默认的填充封闭区域背景的画刷。如果我们想更换背景颜色,需要选入其他系统预定义的画刷(BLACK_BRUSH、GRAY_BRUSH、NULL_BRUSH)。我们也可以选择创建并选入自定义的画刷。此外,我们还需要关注与填充背景相关的背景模式(透明模式和非透明模式)、填充模式(ALTERNATE模式和WINDING模式)和绘图模式。
本节必须掌握的知识点:
多边形填充模式
第26练:绘制填充区域
画刷
4.4.1 多边形填充模式
Windows使用当前被选入设备环境的画笔来绘制图形的边框线。边框线使用当前的背 景模式、背景颜色和绘图模式,这与Windows绘制线条一样。我们所学的关于线条的所有 知识都适用于这些图形的边框线。下表列出了 Windows用于绘制带有边框的填充区域的7个函数。
函数名称 | 图形 |
Rectangle | 直角矩形 |
Ellipse | 椭圆 |
RoundRect | 圆角矩形 |
Chord | 弓形,由椭圆圆周上的弧和一根弦组成 |
Pie | 椭圆上的一个扇形 |
Polygon | 多边形 |
PolyPolygon | 多个多边形 |
表4-2 带有边框的填充区域函数
Windows使用当前选入设备环境的画刷来填充图形。在默认情况下,使用的是备用对象WHITE_BRUSH,这就意味着使用白色来绘制图形内部。
Windows定义了 6种备用画刷: WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH 和NULL_BRUSH(又称为HOLLOW_BRUSH)。和选择备用画笔一样,可以将任何一种备用画刷选入设备环境。Windows定义画刷的句柄为HBRUSH类型,所以要先定义一个画 刷句柄变量:
HBRUSH hBrush;
获取GRAY_BRUSH句柄可以通过调用如下的GetStockObject函数实现:
hBrush = GetStockObject (GRAY_BRUSH);
然后通过调用SelectObject函数将它选进设备环境:
SelectObject (hdc, hBrush);
现在,当绘制上表中的任意图形时,内部都会变成灰色。
如果要绘制一个不含边框线的图形,则要把NULL_PEN画笔选入设备环境:
SelectObject (hdc, GetStockObject (NULL_PEN));
如果只想绘制图形的边框线,而不想填充图形的内部,可以将NULL_BRUSH选入设备环境:
SelectObject (hdc, GetStockobject (NULL_BRUSH);
也可以建立自定义的画刷,就像建立自定义的画笔一样。我们将在下一小节中介绍。
■Polygon函数和多边形填充模式
Polygon函数用于绘制一个多边形形状。以下是Polygon函数的常见原型:
BOOL Polygon(
HDC hdc, // 设备上下文句柄
const POINT* points, // 多边形的顶点数组指针
int count // 顶点的数量
);
Polygon函数用于在设备上下文中绘制一个多边形形状。多边形由一系列顶点构成,通过指定顶点的坐标数组来定义。多边形的绘制遵循顺时针顺序连接顶点的规则。
举例
以下是一个示例代码,演示如何使用Polygon函数绘制一个三角形:
HDC hdc = GetDC(hwnd); // 获取窗口的设备上下文
POINT points[3]; // 三角形的顶点数组
points[0].x = 100; points[0].y = 100; // 第一个顶点
points[1].x = 200; points[1].y = 200; // 第二个顶点
points[2].x = 300; points[2].y = 100; // 第三个顶点
Polygon(hdc, points, 3); // 绘制三角形
ReleaseDC(hwnd, hdc); // 释放设备上下文
上述代码中,我们创建了一个包含三个顶点的POINT数组,并将顶点的坐标赋值。然后调用Polygon函数传递顶点数组和顶点数量来绘制三角形。
需要注意的是,绘制的多边形将使用当前设备上下文的绘图属性,如线条颜色、填充模式等。
PolyPolygon函数的调用形式如下:
PolyPolygon (hdc, apt, aiCounCs, iPolyCounC);
这个函数会绘制多个多边形。最后一个参数是绘制的多边形的个数。对每个多边形,数组 aiCounts给出了多边形顶点的个数。数组apt含有全部多边形的所有顶点。除了返回值外, PolyPolygon在功能上等同于下面的代码:
for (i = 0, iAccum = 0 ; i < iPolyCount ; i++)
{
Polygon (hdc, apt + iAccum, aiCounts[i]) ;
iAccum += aiCounts[i] ;
}
对Polygon和PolyPolygon函数,Windows都使用设备环境中的当前画刷来填充区域。 至于内部是如何填充的,要取决于多边形的填充模式,可以调用SetPolyFillMode函数来设置:
SetPolyFillMode (hdc, iMode);
在默认情况下,多边形的填充模式是ALTERNATE(交替)但是也可以将它设定为 WINDING(螺旋)。这两种方式的区别如图4-6所示。
●ALTERNATE和WINDING模式的区别
“ALTERNATE” 和 “WINDING”是用来处理复杂路径(即,路径中有交叉或重叠部分)的填充规则,本质上是决定哪些区域是需要被填充,哪些区域是应该被排除的。
1.ALTERNATE:也称为奇偶规则。该规则通过画直线从需要考虑填充的区域到区域外部,如果这个线穿过路径的交叉点次数是奇数,则算作在路径内部,该区域会被填充,如果是偶数,则不会被填充。
2.WINDING:也称为非零环绕数规则。按照这个规则,所有的路径都是有方向的。从一个区域向外画射线,路径从左到右穿过射线会加一,从右到左穿过射线会减一,如果最终的结果是非零,那么这个区域视为在路径内部,会被填充。如果结果是零,那么这个区域不会被填充。
图4-6用两种多边形填充模式绘制的图:ALTERNATE(左)和WINDING(右)
4.4.2 第26练:绘制填充区域
/*------------------------------------------------------------------
026 WIN32 API 每日一练
第26个例子ALTWIND.C:绘制填充区域
SetPolyFillMode函数
Polygon函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("AltWind.C") ;
…(略)
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static POINT apt[10];
//多边形顶点坐标数组
static POINT aptFigure[10] = { {10,70}, 50,70, 50,10, 90,10, 90,50,
30,50, 30,90, 70,90, 70,30, 10,30 };
static int cxClient,cyClient;
HDC hdc ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0 ;
case WM_PAINT :
hdc = BeginPaint(hwnd,&ps);
SelectObject(hdc,GetStockObject(GRAY_BRUSH));
//设置顶点坐标---左图
for (int i = 0;i < 10;i++)
{
apt[i].x = cxClient *aptFigure[i].x/200;
apt[i].y = cyClient *aptFigure[i].y/200;
}
//设置ALTERNATE填充模式
//选择交替模式(填充每条扫描线上奇数和偶数多边形边之间的区域)
SetPolyFillMode(hdc,ALTERNATE);
Polygon(hdc,apt,10);//绘制多边形
//重置顶点坐标---左图
for (int i = 0;i < 10;i++)
{
apt[i].x += cxClient/2;
}
//设置WINDING填充模式
//选择缠绕模式(用非零缠绕值填充任何区域)
SetPolyFillMode(hdc,WINDING);
Polygon(hdc,apt,10);//绘制多边形
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/******************************************************************************
SetPolyFillMode函数:设置填补多边形多边形填充模式。
int SetPolyFillMode(
HDC hdc,
int mode //ALTERNATE:选择替代模式,只有该射线穿越奇数条边框线时,封闭区域才会被填充。
//WINDING:选择绕线模式,穿过奇数条边框线或相同方向偶数条边时封闭区域才会被填充。
);
*******************************************************************************
Polygon函数:
绘制由以直线连接两个或更多个顶点的多边形。
使用当前的笔勾勒出多边形的轮廓,并使用当前的笔刷和多边形填充模式填充多边形。
BOOL Polygon(
HDC hdc,
const POINT *apt, //指向POINT结构数组的指针,该数组以逻辑坐标形式指定多边形的顶点。
int cpt //数组中的顶点数。该值必须大于或等于2。
);
*/
运行结果:
图4-7 填充模式
总结
上述实例非常简单,窗口过程中处理WM_PAINT消息时,先调用SelectObject函数选入一个灰色背景画刷,然后使用一个for循环语句初始化一个多边形顶点坐标数组,接着调用SetPolyFillMode函数和SetPolyFillMode函数设置填充模式并绘制多边形。
4.4.3 画刷
在4.3节中我们讲述了如何创建新的自定义画笔。同样的道理,我们也可以创建新的自定义画刷。
Windows允许使用5种函数来建立逻辑画刷。调用SelectObject函数将画刷选入设备 环境。逻辑画刷也是GDI对象,这和逻辑画笔一样。所以所有你建立的画刷都必须最终被删除,但是如果它当前被选入了设备环境中,则不要删除它。
■建立逻辑画刷的第一个函数
hBrush = CreateSolidBrush (crColor);
这个函数中的Solid(中文含义为实心)并不意味着画刷为纯色。将该画刷选入设备环境后, Windows可能会建立一个抖动位图,并且使用它作为画刷。
■建立逻辑画刷的第二个函数
可以使用由水平、垂直或者对角线组成的“阴影线标记”(hatchmark)来建立一个画刷。这种样式的画刷对着色条形图的内部和在绘图机上绘图最常用。建立阴影线画刷的函数如下:
hBrush = CreateHatchBrush (iHatchSCyle, crColor);
参数iHatchStyle表示阴影线标记的外观。图4-8显示了 6种可用的明影线样式及其外观。
图4-8 6种阴影线固刷样式
CreateHatchBrush函数中的参数crClolor表示阴影线的颜色。把画刷选入设备环境后, Windows把这种颜色转换为显示器上可用的最近的纯色。阴影线之间的区域会使用设备环 境中定义的背景模式和背景颜色来着色。如果背景模式是OPAQUE,则背景颜色(也被转换 为纯色)用来填充线与线之间的空隙。如果背景的模式是TRANSPARENT,则Windows只画出阴影线,不填充它们之间的空隙。
■建立逻辑画刷的第三和第四个函数
可以通过函数CreatePatternBrush和CreateDIBPatternBrushPt来建立自己的位图画刷。我们将在第十四章位图详细讲解。
■建立逻辑画刷的第五个函数
建立逻辑画刷的第5个函数包含其他4个函数的所有功能:
hBrush = CreateBrushlndirect (&logbrush);
变量logbrush是一个类型为LOGBRUSH( “逻辑画刷”)的结构。这个结构有三个字段,lbStyle字段的值决定着Windows如何解释其他的两个字段。
LOGBRUSH结构体(三个字段,lbStyle 字段的值决定Windows如何解释其他两个字段) | ||
lbStyle(UINT) | lbColor(COLORREF) | lbHatch(LONG) |
BS_SOLID | 画刷的颜色 | 被忽略 |
BS_HOLLOW | 被忽略 | 被忽略 |
BS_HATCHED | 阴影线的颜色 | 阴影线画刷的样式 |
BS_PATTERN | 被忽略 | 位图的句柄 |
BS_DIBPATTERNPT | 被忽略 | 指向DIB的指针 |
■选入画刷和删除画刷
画刷作为GDI对象,同样必须遵循GDI对象三原则。在前面我们调用SelectObject函数将逻辑画笔选入设备环境,调用DeleteObject函数来 删除这个逻辑画笔,调用GetObject函数来获取逻辑画笔的信息。对于画刷,同样可以使用 这三个函数。一旦拥有一个画刷句柄,就可以调用SelectObject函数将其选入设备环境:
SelectObject (hdc, hBrush);
然后,调用DeleteObject函数删除已建立的画刷:
DeleteObject (hBrush);
但是不要删除当前被选入设备环境的画刷。
如果需要获取画刷的信息,可以调用GetObject函数:
GetObject (hBrush, sizeof (logbrush), (LPVOID) slogbrush);
其中,logbrush是一个类型为LOGBRUSH的结构。