理论上,所有的图形设备驱动程序所需要的就是SetPixel函数和GetPixel函数。其余的一切都可以使用在GDI模块中实现的更高层的例程来处理。例如,画一条线,GDI可以不停地调整x和y坐标,然后连续调用多次SetPixel函数来实现。
事实上,仅使用SetPixel和GetPixel函数,确实可以绘制几乎所有需要的图形。也可以在这些函数的基础上设计一个简洁好用的结构良好的图形程序系统。唯一的问题是性能。 如果一个函数需要多次调用SetPixel函数,那么它执行起来会非常慢。如果一个图形系统是建立在设备驱动程序的层次上进行画线操作和其他复杂图形操作,它就会有效得多。因为设备驱动程序对完成实际操作的程序代码进行了最优化处理。更不用说一些显卡还包含允许视频硬件直接进行绘图的图形处理器。
本节必须掌握的知识点:
点和直线
第23练:绘制正玄波曲线
第24练:绘制矩形、椭圆和圆角矩形
第25练:绘制贝塞尔曲线
画笔
背景模式和绘图模式
4.3.1 点和直线
■像素点绘图函数
在Windows程序中设置像素点需要使用SetPixel和GetPixel函数(虽然很少会用)。
●SetPixel函数是GDI(图形设备接口)中的一个函数,用于在设备上下文(Device Context)中设置指定坐标处的像素颜色。
函数的原型如下:
COLORREF SetPixel(
HDC hdc, //设备上下文句柄,指定了要进行绘制的设备上下文。
int x, //指定要设置像素的横坐标。
int y, //指定要设置像素的纵坐标。
COLORREF color//所设置像素的颜色,使用COLORREF类型表示,可以通过RGB值或
系统定义的颜色常量指定。
);
该函数会将指定坐标处的像素颜色修改为指定的颜色,并返回先前的颜色值(COLORREF类型)。如果函数调用失败,则返回CLR_INVALID。
需要注意的是,SetPixel函数由于每次只能修改一个像素的颜色,所以在大量绘制像素时可能会比较慢,不适合对整个图形进行绘制,或者需要高效绘制的场景。在需要绘制大量像素的情况下,使用 SetPixel 函数效率较低。通常,绘制大规模图像或复杂图形时,更推荐使用其他更高效的方式,如使用位图、图形路径、图形对象等。
如果你需要在设备上下文中进行大规模像素操作,可以考虑使用以下方法之一:
1.使用 BITMAP 和图形对象:可以创建一个位图(Bitmap),利用图形对象进行绘制。首先,创建一个与设备上下文兼容的位图,并使用 SelectObject 函数将其选择到设备上下文中。然后,可以使用位图的绘图函数,如 SetPixel, SetDIBitsToDevice 或者 StretchDIBits来进行绘制。这样可以实现对大量像素的高效操作。我们将在第十四章详细讲述位图。
2.使用缓冲区:可以使用内存缓冲区作为临时存储来进行像素计算和绘制操作,然后将绘制结果一次性地复制到设备上下文中。这种方式可以避免频繁的像素操作,提高绘制效率。
使用 GDI+:GDI+ 是 Windows 提供的一个高级图形库,它提供了更多灵活和高效的图像处理功能。通过使用 GDI+,可以使用其提供的图形对象和方法来进行高效的图像绘制和处理。这些方法可以根据具体的需求按照情况选择,以实现绘制大量像素时的高效操作。
●GetPixel 函数是 Windows GDI 的一部分,用于获取设备上下文(Device Context, DC)中指定坐标点的颜色值。
函数的原型如下:
COLORREF GetPixel(
HDC hdc, //指定设备上下文句柄。
int nXPos,// 指定要检查的点的 X 坐标(水平位置)。
int nYPos //指定要检查的点的 Y 坐标(垂直位置)。
);
返回值:
如果函数调用成功,返回值是该点的COLORREF颜色值,该颜色值包含了 RGB (红绿蓝) 成分。
如果函数调用失败,返回值是 CLR_INVALID。
GetPixel 可以用来读取屏幕上任意点的颜色值或者设备上下文(比如内存DC)中的颜色值。这个功能在图形软件开发中常常用来进行颜色拾取、屏幕取色等操作。然而,由于 GetPixel 需要在调用时进行设备上下文的交互来获取单独的像素信息,频繁使用这一函数可能会影响应用的性能。在处理大量像素点的情况下,通常会采用更复杂但效率较高的方法,比如直接读取位图数据等。
■直线绘图函数
在Windows平台上,可以使用GDI(图形设备接口)函数来进行直线绘图。以下是一些常用的用于绘制直线的GDI函数:
●MoveToEx(移动到指定点):将绘图设备的当前位置移动到指定点。
BOOL MoveToEx(
HDC hdc, // 绘图设备的句柄
int X, // 目标点的X坐标
int Y, // 目标点的Y坐标
LPPOINT lpPoint // 可选参数,用于获取移动前的当前位置
);
函数的返回值为非零表示成功,零表示失败。
当你在画直线或者其他形状的时候,首先使用 MoveToEx 设定起始点,然后使用类似 LineTo 的函数去绘制到目标位置。这样就形成了一次从起始点到结束点的绘制过程。
●LineTo(绘制直线):从当前位置绘制一条直线到指定点。
BOOL LineTo(
HDC hdc, // 绘图设备的句柄
int X, // 目标点的X坐标
int Y // 目标点的Y坐标
);
返回值:如果函数成功,返回非零值;如果失败,返回零。
●Polyline是Windows平台上用于绘制一系列相连线段的GDI函数。它会连接给定的一组点,绘制相邻点之间的线段,最后一个点不会与下一个点连接。
以下是Polyline函数的函数原型:
BOOL Polyline(
HDC hdc, // 绘图设备的句柄
const POINT* lppt, // 包含点坐标的数组指针
int cPoints // 点的数量
);
●PolylineTo函数则是在当前位置和指定点之间绘制一段连续的线段。它会从当前位置开始,绘制连接当前位置和每个指定点的线段。函数原型如下:
BOOL PolylineTo(
HDC hdc, // 绘图设备的句柄
const POINT* lppt, // 包含点坐标的数组指针
DWORD cPoints // 点的数量
);
该函数的返回值为BOOL,当函数成功时返回非零值,失败时返回零。
PolylineTo和MoveToEx一起使用可以绘制一系列的连线。使用MoveToEx设定起始点,然后PolylineTo绘制从当前位置到每个POINT所定义的点的连续线段。使用PolylineTo的优势在于,你不需要在每两个点之间重复调用MoveToEx和LineTo来绘制线段,因此可以提高程序的效率。
在绘制操作结束后,当前位置会更新为PolylineTo函数最后一个点的位置。
●PolyPolyline函数可以用于绘制由多个不同点数的多段连续直线组成的复杂图形。
以下是PolyPolyline函数的函数原型:
BOOL PolyPolyline(
HDC hdc, // 绘图设备的句柄
const POINT* apt, // 包含所有线段端点坐标的数组指针
const DWORD* adwPolyPoints, // 包含每个多段连续直线的点数的数组指针
DWORD cCount // 多段连续直线的数量
);
函数返回非零表示成功,返回零表示失败。
●Arc函数是Windows平台上的一个GDI函数,用于绘制圆弧或椭圆弧。
以下是Arc函数的函数原型:
BOOL Arc(
HDC hdc, // 绘图设备的句柄
int nLeftRect, // 弧的左上角矩形的左边界坐标
int nTopRect, // 弧的左上角矩形的上边界坐标
int nRightRect, // 弧的左上角矩形的右边界坐标
int nBottomRect, // 弧的左上角矩形的下边界坐标
int nXStartArc, // 弧的起点的 x 坐标
int nYStartArc, // 弧的起点的 y 坐标
int nXEndArc, // 弧的终点的 x 坐标
int nYEndArc // 弧的终点的 y 坐标
);
函数返回非零表示成功,返回零表示失败。 这个函数在绘制一段椭圆弧时非常有用,例如绘制饼图时。但需要注意的是,这个弧是在由 nLeftRect,nTopRect,nRightRect,nBottomRect 定义的矩形框内绘制的,因此可能不是完全的圆弧。
●ArcTo函数用于在设备上下文中绘制一个弧。相比 Arc 函数,ArcTo 函数更灵活,可以用来创建更复杂的路径。其函数原型如下:
BOOL ArcTo(
HDC hdc, // 绘图设备的句柄
int nLeftRect, // 弧线段所在椭圆的左上角矩形的左边界坐标
int nTopRect, // 弧线段所在椭圆的左上角矩形的上边界坐标
int nRightRect, // 弧线段所在椭圆的左上角矩形的右边界坐标
int nBottomRect, // 弧线段所在椭圆的左上角矩形的下边界坐标
int x1, // 弧线段的起点的 x 坐标
int y1, // 弧线段的起点的 y 坐标
int x2, // 弧线段的终点的 x 坐标
int y2 // 弧线段的终点的 y 坐标
);
ArcTo函数用于绘制从当前位置到指定点的弧线段,弧线段位于指定椭圆的边界上。当前位置是通过之前的绘图操作确定的。ArcTo函数会自动将当前位置移动到弧线段的终点。
●AngleArc是用来绘制圆弧的函数,这个函数可以设置弧度的大小,使得绘制的线条更具灵活性。其函数原型如下:
BOOL AngleArc(
HDC hdc, // 绘图设备的句柄
int x, // 椭圆的中心点的 x 坐标
int y, // 椭圆的中心点的 y 坐标
DWORD dwRadius, // 椭圆的半径
FLOAT eStartAngle, // 起始角度(度数,逆时针方向为正)
FLOAT eSweepAngle // 弧线的角度范围(度数,逆时针方向为正)
);
函数返回非零表示执行成功,返回零表示执行失败。
●AngleArc 函数鉴于其灵活性,经常用于绘制如刻度盘、时钟等应用中,需要根据角度进行细致控制的场景。
PolyBezier 是 Windows GDI (图形设备接口) 中用来绘制贝塞尔曲线的函数。贝塞尔曲线是一种非常流行的平滑曲线,广泛应用于计算机图形学中。其函数原型如下:
BOOL PolyBezier(
HDC hdc, // 绘图设备的句柄
const POINT *lppt, // 包含贝塞尔曲线顶点的数组指针
DWORD cPoints // 贝塞尔曲线顶点的数量
);
如果函数执行成功,返回非零;如果执行失败,返回零。
这个函数可以帮你创建很多复杂和平滑的图形,比如字型设计、动画制作和矢量图形等领域经常用到。
可以使用 MoveToEx 函数设置起始点,然后使用 PolyBezier 函数绘制贝塞尔曲线。
●PolyBezierTo用于绘制一系列的贝塞尔曲线。它和 PolyBezier 函数类似,不过 PolyBezierTo 的初点是当前的位置点。其函数原型如下:
BOOL PolyBezierTo(
HDC hdc, // 绘图设备的句柄
const POINT *lppt, // 包含贝塞尔曲线顶点的数组指针
DWORD cCount // 贝塞尔曲线顶点的数量
);
如果函数执行成功,返回非零;如果函数执行不成功,返回值为零。
使用 PolyBezierTo 函数能够创建连续且平滑的曲线,它被广泛应用于动画制作,字体设计,矢量图形设计等计算机图形领域。
●PolyDraw用于一次绘制一在设备上下文中绘制一系列的线段和贝塞尔曲线的函数。其中每一段和每一条曲线可以是闭合的也可以是打开的。其函数原型如下:
BOOL PolyDraw(
HDC hdc, // 绘图设备的句柄
const POINT *lppt, // 包含线段顶点的数组指针
const BYTE *lpbTypes, // 包含线段类型的数组指针
int cCount // 顶点和类型的数量
);
如果函数执行成功,返回非零;如果函数执行失败,返回值为零。
这个函数允许在一个操作中绘制一系列复杂的图形,可以同时包括直线和曲线,大大提高了绘制的效率和灵活性,常用于复杂图形的绘制。
提示
上述GDI绘图函数通常会有两个版本,一个版本是“To”结尾的函数,一个没有“To”结尾。它们的区别是“To”结尾的函数会改变绘图的起始坐标,将起始坐标改为绘图结束后的最后一个位置。而不带“To”结尾的函数则不会改变绘图的起始坐标。显然,如果连续绘图,使用“To”结尾的函数不需要重新设定起始坐标,更为方便灵活。
■边框绘图函数
●Rectangle函数用于在设备上下文中绘制一个矩形。其函数原型如下:
BOOL Rectangle(
HDC hdc, // 绘图设备的句柄
int left, // 矩形左上角的 x 坐标
int top, // 矩形左上角的 y 坐标
int right, // 矩形右下角的 x 坐标
int bottom // 矩形右下角的 y 坐标
);
如果函数执行成功,返回非零;如果函数执行失败,返回值为零。
Rectangle 函数是 GDI 中最常用的绘图函数,它可以在图形绘制中快速的创建一个矩形形状。
●Ellipse函数用于在设备上下文中绘制一个椭圆形。其函数原型如下:
BOOL Ellipse(
HDC hdc, // 绘图设备的句柄
int left, // 椭圆的左上角的 x 坐标
int top, // 椭圆的左上角的 y 坐标
int right, // 椭圆的右下角的 x 坐标
int bottom // 椭圆的右下角的 y 坐标
);
如果函数执行成功,返回非零;如果函数执行失败,返回值为零。
Ellipse 函数可以在图形界面中快速绘制一个椭圆形,当需要绘制圆形时,只需确保包含椭圆的矩形是正方形(即宽和高相等)就可以了。
●RoundRect函数主要用于绘制带有圆角的矩形。其函数原型如下:
BOOL RoundRect(
HDC hdc, // 绘图设备的句柄
int left, // 矩形左上角的 x 坐标
int top, // 矩形左上角的 y 坐标
int right, // 矩形右下角的 x 坐标
int bottom, // 矩形右下角的 y 坐标
int width, // 圆角的宽度
int height // 圆角的高度
);
如果函数执行成功,返回非零;如果函数执行失败,返回值为零。
RoundRect 函数可以绘制带有圆角的矩形,这在绘制用户界面,如按钮等组件时很有用。如果你希望绘制的圆角是一个正圆,那么你需要让 width 和 height 相等。
●Pie函数主要用于绘制一个饼图形。其函数原型如下:
BOOL Pie(
HDC hdc, // 绘图设备的句柄
int left, // 扇形或椭圆弧的外接矩形的左上角的 x 坐标
int top, // 扇形或椭圆弧的外接矩形的左上角的 y 坐标
int right, // 扇形或椭圆弧的外接矩形的右下角的 x 坐标
int bottom, // 扇形或椭圆弧的外接矩形的右下角的 y 坐标
int xRadial1, // 扇形或椭圆弧的起点与椭圆中心的连线与 x 轴的夹角度数
int yRadial1, // 扇形或椭圆弧的起点与椭圆中心的连线与 y 轴的夹角度数
int xRadial2, // 扇形或椭圆弧的终点与椭圆中心的连线与 x 轴的夹角度数
int yRadial2 // 扇形或椭圆弧的终点与椭圆中心的连线与 y 轴的夹角度数
);
如果函数执行成功,返回非零;如果函数执行失败,返回值为零。
Pie 函数可以绘制一个饼图形,其填充使用当前的刷子,边缘线采用当前的画笔。在绘制统计图表或者需要部分椭圆形的场合中,该函数非常有用。
●Chord函数用于绘制一个弦图,即一个由线段和弧线段组成的闭合图形。其函数原型如下:
void drawChord(
SDL_Renderer* renderer, // 渲染器对象,用于绘制图形的画布
int centerX, // 和弦图的中心点横坐标
int centerY, // 和弦图的中心点纵坐标
int radius, // 和弦图的半径
int* connections, // 和弦图的节点之间的连接关系数组
int numNodes, // 节点数量
SDL_Color* colors // 节点颜色数组
);
如果函数成功,返回非零;若失败,则返回零。
Chord 函数在绘制物理图像,如科学图表和统计图表,或游戏中的图像素材等场合特别有用。通过控制其参数,可以构造出各种形状的图形。
提示
所有的GDI绘图函数都需要一个绘图设备的句柄(HDC)作为参数,可以通过调用GetDC函数获取窗口的绘图设备句柄。
4.3.2 第23练:绘制正玄波曲线
/*------------------------------------------------------------------
023 WIN32 API 每日一练
第23个例子SINEWAVE.C:绘制正弦波曲线
MoveToEx函数
LineTo函数
Polyline函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
#include <math.h>
#define NUM 1000
#define TOWPI (2*3.14159)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("DEVCAPS1.C") ;
…(略)
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient,cyClient;
HDC hdc ;
int i;
PAINTSTRUCT ps ;
POINT apt[NUM];
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);//客户区的宽度
cyClient = HIWORD(lParam);//客户区的高度
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
//画X轴
MoveToEx(hdc, 0, cyClient / 2, NULL);//将绘图起点坐标移动到指定点
LineTo(hdc, cxClient, cyClient / 2);//绘制中轴线
//计算并填充正玄波曲线坐标数组
for (i = 0; i < NUM; i++)
{
apt[i].x = i * cxClient / NUM;
apt[i].y = (int)(cyClient / 2 * (1 - sin(TOWPI*i / NUM)));
}
//绘制正玄波曲线,当前坐标不变
Polyline(hdc,apt,NUM);//一次绘制完成,比LineTo的效率高的多
//PolylineTo(hdc, apt, NUM);//对比测试
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/******************************************************************************
MoveToEx函数功能更新当前位置到指定点和可选返回原来的位置。
BOOL MoveToEx(
HDC hdc, //设备上下文句柄
int x, //指定新位置的X坐标(以逻辑单位为单位)
int y, //指定新位置的Y坐标(以逻辑单位为单位)
LPPOINT lppt //指向接收先前或当前位置的POINT结构的指针。如果此参数是NULL指针,则不返回前一个位置。
);
*******************************************************************************
LineTo函数绘制从当前位置到一条线,但不包括当前指定点
BOOL LineTo(
HDC hdc, //设备上下文句柄
int x, //指定新位置的X坐标(以逻辑单位为单位)
int y //指定新位置的Y坐标(以逻辑单位为单位)
);
*******************************************************************************
Polyline函数,画一条由多条首尾相连的直线构成的折线
BOOL Polyline(
HDC hdc, //设备上下文句柄
const POINT *apt, //指向包含逻辑单元顶点的POINT结构数组的指针
DWORD cpt //数组中的点数
);
Polyline函数和PolylineTo函数的区别:
Polyline函数不使用和改变当前位置。
PolylineTo函数则有点不同, 它使用当前位置作为起始点,
画完线后,在返回前会将当前位置设置为最后一根线的终点
*/
运行结果:
图4-2 正弦波曲线
总结
实例SINEWAVE.C在处理WM_PAINT消息时,首先获取设备环境句柄,然后调用MoveToEx和LineTo函数会在中轴线。最后调用Polyline函数会在一条正弦波曲线。
动手实验:对比Polyline函数与PolylineTo函数的区别:
●Polyline函数:
Polyline函数是一次性地绘制多条连接的线段。
该函数接受一个点数组作为输入,按照数组中点的顺序依次绘制线段。
绘制的线段是连续的,每个点都与其前一个点和后一个点相连。
Polyline函数在绘制完所有线段后会自动闭合路径,即将最后一个点与第一个点相连。
●PolylineTo函数:
PolylineTo函数是逐段绘制线段的方式。
该函数接受一个点数组作为输入,从当前绘图位置开始逐段绘制线段。
每次调用PolylineTo函数时,它会从当前位置绘制一条线段到指定的点,并将当前位置移动到该点。
在绘制完所有线段后,路径不会自动闭合,需要手动调用闭合路径函数(如ClosePath)将最后一个点与起始点相连,形成封闭的多边形路径。
总结:
Polyline函数一次性绘制多条连接的线段,并自动闭合路径。
PolylineTo函数逐段绘制线段,每次绘制后将当前位置移动到下一个点,路径需要手动闭合。
4.3.3 第24练:绘制矩形、椭圆和圆角矩形
/*------------------------------------------------------------------
024 WIN32 API 每日一练
第24个例子LINEDEMO.C:绘制矩形、椭圆和圆角矩形
Rectangle函数
Ellipse函数
RoundRect函数
(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 ("LineDemo.C") ;
…(略)
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
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(WHITE_PEN));//选入白色画笔
//绘制矩形边框
Rectangle(hdc,cxClient/8,cyClient/8,7*cxClient/8,7*cyClient/8);
//绘制两条对角线
MoveToEx(hdc,0,0,NULL);
LineTo(hdc,cxClient,cyClient);
MoveToEx(hdc,0,cyClient,NULL);
LineTo(hdc,cxClient,0);
//SelectObject(hdc,GetStockObject(NULL_PEN));//选入空画笔
//绘制椭圆
Ellipse(hdc,cxClient/8,cyClient/8,7*cxClient/8,7*cyClient/8);
//绘制圆角矩形
RoundRect(hdc,cxClient/4,cyClient/4,3*cxClient/4,
3*cyClient/4,cxClient/4,cyClient/4);
//SelectObject(hdc, GetStockObject(BLACK_PEN));//选入黑色画笔
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/*************************************************************
Rectangle函数:绘制矩形边框
BOOL Rectangle(
HDC hdc,
int left,
int top,
int right,
int bottom
);
参数为矩形4个点的坐标
***************************************************************
Ellipse函数:绘制椭圆边框,椭圆的中心是指定边界矩形的中心。
使用当前的笔勾勒出椭圆,并使用当前的笔刷填充椭圆。
BOOL Ellipse(
HDC hdc,
int left,
int top,
int right,
int bottom
);
参数为边界矩形4个点的坐标
*******************************************************************
RoundRect函数:绘制圆角矩形边框,并使用当前的笔勾勒出矩形并使用当前的笔刷填充。
BOOL RoundRect(
HDC hdc,
int left,
int top,
int right,
int bottom,
int width,
int height
);
参数为边界矩形4个点的坐标+圆角的宽和高
*/
运行结果:
图4-3 矩形、对角线椭圆和圆角矩形
总结
实例的窗口过程在处理WM_PAINT消息时,分别调用Rectangle函数会在矩形,调用MoveToEx和LineTo函数会在两天对角线,调用Ellipse函数会在椭圆,调用RoundRect函数会在一个圆角矩形。需要注意以下几点:
1.会在矩形、椭圆和圆角矩形都是在矩形范围内会在,它们的参数都包含一个矩形坐标(左上角和右下角RECT坐标)。因此,矩形和直线上GDI绘图函数的基本图形。
2.注意绘图的先后顺序,后面绘制的图形会覆盖前面绘制的图形。
3.动手实验:测试调用SelectObject函数分别选入白色画笔、空画笔或黑色画笔的不同效果。
4.3.4 第25练:绘制贝塞尔曲线
/*------------------------------------------------------------------
025 WIN32 API 每日一练
第25个例子BEZIER.C:绘制贝塞尔样条曲线
PolyBezier函数
PolyBezierTo函数
(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 ("Bezier.C") ;
…(略)
return msg.wParam ;
}
/*PolyBezier贝塞尔样条曲线绘制函数
void DrawBezier(HDC hdc,POINT apt[])
{
//画贝塞尔曲线
PolyBezier(hdc,apt,4);
//画两条控制线
MoveToEx(hdc,apt[0].x,apt[0].y,NULL);
LineTo(hdc,apt[1].x,apt[1].y);
MoveToEx(hdc, apt[2].x, apt[2].y, NULL);
LineTo(hdc, apt[3].x, apt[3].y);
}*/
/*PolyBezierTo贝塞尔样条曲线绘制函数*/
void DrawBezier(HDC hdc, POINT apt[])
{
//画贝塞尔曲线
MoveToEx(hdc, apt[0].x, apt[0].y, NULL);
POINT szApt[] = { apt[1],apt[2],apt[3] };
PolyBezierTo(hdc, szApt, 3);
//画两条控制线
MoveToEx(hdc, apt[0].x, apt[0].y, NULL);
LineTo(hdc, apt[1].x, apt[1].y);
MoveToEx(hdc, apt[2].x, apt[2].y, NULL);
LineTo(hdc, apt[3].x, apt[3].y);
}
//窗口过程
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static POINT apt[4];
int cxClient,cyClient;
HDC hdc ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
//填充坐标数组
apt[0].x = cxClient/4;
apt[0].y = cyClient/2;
apt[1].x = cxClient / 2;
apt[1].y = cyClient / 4;
apt[2].x = cxClient / 2;
apt[2].y = 3*cyClient / 4;
apt[3].x = 3*cxClient / 4;
apt[3].y = cyClient / 2;
return 0 ;
case WM_LBUTTONDBLCLK://点击鼠标左键
case WM_RBUTTONDBLCLK://点击鼠标右键
case WM_MOUSEMOVE: //拖动鼠标
if ((wParam & MK_LBUTTON) || (wParam & MK_RBUTTON))
{
hdc = GetDC(hwnd);
//用白色画刷重绘一遍,即擦除旧的曲线
SelectObject(hdc,GetStockObject(WHITE_PEN));
DrawBezier(hdc,apt);
if (wParam & MK_LBUTTON)
{
apt[1].x = LOWORD(lParam);
apt[1].y = HIWORD(lParam);
}
if (wParam & MK_RBUTTON)
{
apt[2].x = LOWORD(lParam);
apt[2].y = HIWORD(lParam);
}
//用黑色画笔,绘制曲线
SelectObject(hdc, GetStockObject(BLACK_PEN));
DrawBezier(hdc, apt);
ReleaseDC(hwnd, hdc);
}
return 0;
/*其他消息中的绘图API都由WM_PAINT消息来处理*/
case WM_PAINT :
//使整个窗口客户区无效,并重绘背景,如果窗口客户区显示其他内容,先清除
InvalidateRect(hwnd,NULL,TRUE); //参数NULL默认整个窗口客户区
hdc = BeginPaint(hwnd,&ps);
DrawBezier(hdc,apt);//绘制贝塞尔曲线
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/*******************************************************************************
PolyBezier函数:绘制一个或多个贝塞尔曲线。
BOOL PolyBezier(
HDC hdc, //设备上下文的句柄
const POINT *apt, //指向POINT结构数组指针,数组包含逻辑单位曲线端点和控制点。
DWORD cpt //lppt数组中的点数。该值必须是要绘制的曲线数量的三倍以上,
//因为每条贝塞尔曲线需要两个控制点和一个端点,而初始曲线需要一个附加的起点。
);
前四个点(按照顺序)分别表示第一条贝塞尔样条曲线的起点、第一个控点、第二个控点和终点。
随后的每一条贝塞尔样条曲线则只需要给出三个点,
因为前一条贝塞尔样条曲线的终点就是后一条贝塞尔样条曲线的起点,如此类推。
*******************************************************************************
PolyBezierTo用于将当前画笔位置设为前一条曲线的终点。
void cairo_poly_bezier_to(
cairo_t* cr, // 绘图上下文对象
const cairo_point_t* points, // 贝塞尔曲线段的控制点数组
int num_points // 控制点的数量
);
*******************************************************************************
InvalidateRect函数
添加一个矩形到指定窗口的更新区域。更新区域代表必须重新绘制的窗口工作区的一部分。
BOOL InvalidateRect(
HWND hWnd,//其更新区域已更改的窗口的句柄。如果此参数为NULL,则系统将使所有窗口。不建议将此参数设置为NULL。
const RECT *lpRect,//指向RECT结构的指针,该结构包含要添加到更新区域的矩形的客户坐标。如果此参数为NULL,则整个工作区将添加到更新区域。
BOOL bErase//指定在处理更新区域时是否要擦除更新区域内的背景。
//如果此参数为TRUE,则在调用BeginPaint函数时将擦除背景。如果此参数为FALSE,则背景保持不变。
);
*/
运行结果:
图4-4 贝塞尔曲线
总结
(一)、贝塞尔曲线
贝塞尔曲线是一种常用于图形绘制的数学曲线,它由控制点决定形状。贝塞尔曲线可以描述平滑的曲线路径,常见于计算机图形学、计算机辅助设计(CAD)和动画等领域。
贝塞尔曲线的形状由控制点的位置和权重来控制。根据控制点的数量,贝塞尔曲线可以分为以下几种常见类型:
1.二次贝塞尔曲线(Quadratic Bezier Curve):
由三个控制点定义,分别为起始点、控制点和结束点。
曲线路径受控制点和控制点之间的权重影响。
二次贝塞尔曲线的方程可以用参数方程表示。
2.三次贝塞尔曲线(Cubic Bezier Curve):
由四个控制点定义,分别为起始点、两个控制点和结束点。
曲线路径受控制点和控制点之间的权重影响。
三次贝塞尔曲线的方程可以用参数方程表示。
贝塞尔曲线的绘制通常通过使用绘图库或图形处理库的相关函数来实现。在绘图库中,常见的函数如PolyBezierTo可以用来绘制贝塞尔曲线段,通过指定控制点的坐标来定义贝塞尔曲线的形状。
贝塞尔曲线具有很好的平滑性和灵活性,可以用于绘制各种曲线形状,如弧线、圆角、自然曲线等。在计算机图形学和设计中,贝塞尔曲线被广泛应用于绘制曲线路径、路径动画、字体设计、图形编辑等方面。
(二)、PolyBezier与PolyBezierTo函数的区别
PolyBezier和PolyBezierTo函数是用于绘制贝塞尔曲线的两种不同方法。
1.PolyBezier函数:PolyBezier函数是一个直接描述整条贝塞尔曲线的函数。它接受一系列的点作为参数,这些点定义了曲线的控制点和结束点。
2.PolyBezierTo函数:PolyBezierTo函数是基于当前路径的绘制操作,它通过添加一个贝塞尔曲线段来延伸路径。PolyBezierTo函数需要指定的参数是贝塞尔曲线的控制点,其中起点是当前路径的最后一个点。
//画贝塞尔曲线
PolyBezier(hdc,apt,4);
等价于:
//画贝塞尔曲线
MoveToEx(hdc, apt[0].x, apt[0].y, NULL);
POINT szApt[] = { apt[1],apt[2],apt[3] };
PolyBezierTo(hdc, szApt, 3);
(三)、InvalidateRect函数用于通知窗口系统需要重新绘制指定区域的部分或全部内容。它的函数原型如下:
BOOL InvalidateRect(
HWND hWnd, // 窗口句柄
const RECT* lpRect, // 指定的矩形区域
BOOL bErase // 是否擦除背景
);
当调用InvalidateRect函数时,它会向窗口系统发送一个重绘(WM_PAINT)消息,告知系统需要更新指定区域的内容。窗口系统在合适的时机会响应这个消息,执行绘制操作,重新绘制被指定区域的内容。
通常,在窗口的消息处理函数中,当需要重新绘制窗口的时候,可以调用InvalidateRect函数来标记需要重绘的区域,然后通过处理WM_PAINT消息来执行实际的绘制操作。
需要注意的是,调用InvalidateRect函数并不会立即引起窗口的重绘,而只是发出了重绘的请求。实际的重绘操作将在系统的消息循环中进行,因此,重绘操作的执行时间是不确定的,它可能会延迟到适当的时候才会执行。
如果想要立即重绘窗口,可以调用UpdataWinow函数立即重绘。
4.3.5 画笔
■使用默认画笔
在实例LINEDEMO.C中,我们使用默认黑色画笔分别绘制了直线、矩形、椭圆和圆角矩形几种不同类型的边框。但是仔细观察代码,我们并没有设置黑色画笔就可以绘制了。这是因为Windows系统默认的画笔就是黑色画笔。当然我们也可以选择其他系统预定义的画笔。
在动手实验中,我们分别给出了Windows系统预定义的仅有的三种画笔:白色画笔(WHITE_PEN)、空画笔(NULL_PEN)和黑色画笔(BLACK_PEN)。不过需要首先调用GetStockObject函数获取系统预定义画笔的句柄,然后再调用SelectObject函数,将画笔句柄选人设备环境hdc中,替换之前设备环境中的画笔,这样就可以使用了。
画笔作为GDI对象,使用时必须符合以下三个原则(所有GDI对象都必须遵守):
1.Windows系统预定义的GDI对象不可以删除。
2.自定义的GDI对象使用完之后必须删除。
3.正在使用中的GDI对象不可以删除。
■创建、选择和删除画笔
●创建画笔
如果想获得更丰富的效果,则必须创建自己的画笔。
这里是创建画笔的一般过程:调用CreatePen或者CreatePenlndirect函数创建一个“逻 辑画笔”,它只是说明你想得到一个什么样的画笔。这些函数会返回一个逻辑画笔的句柄。 然后需要调用SelectObject函数将画笔选入设备环境中。接着,就可以使用这个新的画笔来绘制线条。一次只能有一支画笔被选入设备环境。释放设备环境之后(或者将其他画笔选入设备环境之后),需要调用DeleteObject函数来删除你创建的逻辑画笔。此后,画笔的句柄不再有效。
逻辑画笔是一个“GDI对象”,一个程序可以创建6种GDI对象,它是其中之一,其他5种分别是画刷、位图、区域、字体和调色板。除了调色板之外,所有这些对象都通过SelectObject函数选入设备环境。
1.CreatePen函数:用于创建一个新的画笔对象。
CreatePen函数的原型可能会因所使用的编程语言或图形库而有所不同。以下是在Windows API中的CreatePen函数的常见原型:
HPEN CreatePen(
int fnPenStyle, // 画笔样式
int nWidth, // 画笔宽度
COLORREF crColor // 画笔颜色
);
nPenStyle:画笔的样式
值 | 含义 |
PS_SOLID | 触控笔是实心的。 |
PS_DASH | 触控笔虚线。 仅当笔宽度为 1 或更少(以设备单位为单位)时,此样式才有效。 |
PS_DOT | 笔被点点。 仅当笔宽度为 1 或更少(以设备单位为单位)时,此样式才有效。 |
PS_DASHDOT | 笔具有交替的短划线和点。 仅当笔宽度为 1 或更少(以设备单位为单位)时,此样式才有效。 |
PS_DASHDOTDOT | 笔具有交替的短划线和双点。 仅当笔宽度为 1 或更少(以设备单位为单位)时,此样式才有效。 |
PS_NULL | 笔不可见。 |
PS_INSIDEFRAME | 触控笔是实心的。 在采用边框的任何 GDI 绘图函数中使用此笔时,图形的尺寸会缩小,使其完全适合边界矩形,同时考虑笔的宽度。 这仅适用于几何笔。 |
表4-1 画笔样式
图4-5 画笔样式
对于PS_SOLID、PS_NULL和PSJNSIDEFRAME样式,参数iWidth表示画笔的宽度。 当iWidth值为0时,Windows把画笔的宽度设定为1个像素。备用画笔总是1个像素宽。 如果指定使用虚线或点线样式,同时把画笔宽度设定为大于1个像素,那么Windows会使 用实心的画笔来代替。
CreatePen的参数crColor是一个COLORREF值,它用来指定画笔的颜色。对所有的除 了PS_INSIDEFRAME之外的画笔样式,当将画笔选入到设备环境时,Windows将该颜色转换为设备所能表示的接近的纯色。
PS_INSIDEFRAME 画笔样式是唯一能够使用抖动色的画笔样式,并且只有当画笔宽度大于1时才如此。
PS_INSIDEFRAME画笔样式用于填充区域的函数时有另外一个奇特之处。当使用非PS_INSIDEFRAME 样式的笔样式时,如果用于绘制轮廓的画笔宽度大于1个像素,那么画笔的中心会处于边界之上,这样画出的轮廓线部分将会在边框之外。但是对于PS_INSIDEFRAME 画笔样式,整个轮廊线都会在边框内。
也可以通过建立一个类型为LOGPEN(“逻辑画笔”)的结构,并调用CreatePenlndirect 函数来建立一个画笔。如果你的程序在初始化时需要创建很多不同的画笔,这种方法会很有效。
CreatePen函数用于创建一个新的画笔对象,并返回该画笔对象的句柄(HPEN)。通过该句柄,可以在绘图过程中使用该画笔对象来绘制线条。
创建的画笔对象可以使用SelectObject函数将其选入设备上下文(HDC)中,以便在绘图操作中使用该画笔进行线条绘制。
需要注意的是,使用完创建的画笔对象后,应使用DeleteObject函数将其销毁,以释放相关资源并避免内存泄漏。
2.CreatePenIndirect函数是Windows操作系统中的一个函数,用于创建一个根据指定的逻辑画笔结构(LOGPEN)参数描述的画笔(Pen)对象。它的函数原型如下:
HPEN CreatePenIndirect(
const LOGPEN* lplgpn // 指向LOGPEN结构体的指针
);
LOGPEN结构体定义如下:
typedef struct tagLOGPEN {
UINT lopnStyle; // 画笔样式
POINT lopnWidth; // 画笔宽度
COLORREF lopnColor;// 画笔颜色
} LOGPEN;
CreatePenIndirect函数使用指定的“LOGPEN”结构体参数创建一个新的画笔对象,并返回该画笔对象的句柄(`HPEN`)。通过该句柄,可以在绘图过程中使用该画笔对象来绘制线条。
我们会发现,“LOGPEN”结构体其实就是CreatePen函数的三个参数。可以将CreatePenIndirect函数视为CreatePen函数的另一种形式。在Windows API函数中还有很多类似的情形,后面的章节中我们会陆续接触到。
举例
static HPEN hPen1, hPen2, hPen3;
在处理WM_CREATE消息时,可以创建这三种画笔:
hPen1 = CreatePen (PS_SOLID, 1, 0) ;
hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0)) ;
hPen3 = CreatePen (PS_DOT, 0, 0) ;
●选择画笔
如果是自定义的画笔,调用CreatePenIndirect函数或CreatePen函数都会返回一个新建画笔的句柄,接着调用SelectObject函数将其选入设备上下文(HDC)中,就可以使用该画笔绘图了。
举例
在处理WM_PAINT消息时(或者在任何拥有有效的设备环境句柄时)可以将其中任何一支 画笔选入到设备环境,并且使用它来绘制线条:
SelectObject (hdc, hPen2) ; //选入画笔hPen2
画线函数
SelectObject (hdc, hPen1) ; //绘制完成后,重新选入画笔hPen1
●删除画笔
是否还记得GDI对象三原则,自己创建的GDI对象不用时,需要将其替换出HDC,然后再删除。
删除画笔使用DeleteObject函数:用于销毁(删除)由CreatePen、CreateBrush、CreateFont等函数创建的图形对象,以释放相关资源并避免内存泄漏。
DeleteObject函数的原型可能会因所使用的编程语言或图形库而有所不同。以下是在Windows API中的DeleteObject函数的常见原型:
BOOL DeleteObject(
HGDIOBJ hObject // 图形对象句柄
);
DeleteObject函数用于销毁指定的图形对象,释放相关的资源。一旦对象被销毁,与之相关联的设备上下文(HDC)将不再有效。
在使用完创建的图形对象后,应调用DeleteObject函数将其销毁,以确保资源的释放和避免内存泄漏。这适用于画笔、画刷、字体等各种类型的图形对象。
需要注意的是,只能销毁由GDI函数创建的图形对象。对于其他类型的对象,如窗口句柄(HWND),应使用适当的函数进行销毁。
举例
方法1:
在处理WM_DESTROY消息时,可以删除这三种画笔:
DeleteObject (hPen1) ;
DeleteObject (hPen2) ;
DeleteObject (hPen3) ;
方法2:
可以通过将备用的BLACK_PEN选入到设备 环境中来得到需要被删除的画笔句柄,然后删除它:
DeleteObject (SelectObject (hdc, GetStockObject (black_PEN)));
方法3:
当将一支画笔选入到一个新创建的设备环境时,保存 SelectObject返回的画笔句柄:
hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0)));//hPen为之前的画笔句柄。
如果这是在获得设备环境后第一次调用SelectObject函数,hPen就是 BLACK_PEN的句柄。现在可以在同一条语句中选择该画笔到设备环境,并且删除自己创建的画笔(第二次SelectObject调用返回你创建的画笔的句柄):
DeleteObject (SelectObject (hdc, hPen));
如果有一个画笔的句柄,LOGPEN结构中各个字段成员的值就可以通过调用GetObject 函数获得:
GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen);
如果需要获得当前被选入设备环境的画笔句柄,则调用:
hPen = GetCurrentObjecc (hdc, 0BJ_PEN);
另外一个画笔创建函数:ExtCreatePen,我们将在第十五章讲述。
4.3.6 背景模式和绘图模式
■填充背景颜色
上一小节中我们使用画笔绘制线条构成的基本图形,但是有一个问题,这些图形边框内的间隙怎样填充。这就需要用到画刷。是否还记得,在我们初始化窗口类的时候,选用了Windows系统预定义的白色画刷。
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
如果我们想改变填充色,还可以调用SetBkColor (hdc, crColor);
SetBkColor函数用于设置设备上下文(Device Context)中的背景色。SetBkColor函数的原型:
COLORREF SetBkColor(
HDC hdc, // 设备上下文句柄
COLORREF crColor // 背景色
);
SetBkColor函数用于设置设备上下文的背景色。背景色是绘制操作的背景色彩,例如在填充矩形、绘制文本时的背景色。设置背景色后,绘制操作将使用指定的背景色作为背景。
需要注意的是,SetBkColor函数只设置背景色,并不会立即对设备上下文中的现有绘图进行影响。绘图操作实际上是在调用绘图函数(如TextOut、Rectangle等)时应用背景色。
■设置背景模式
我们经常会见到窗口客户区内显示的文本会有下划线,这是如何实现的呢?这涉及到窗口的背景模式。其实下划线也是一个独立的字符,有着和正常字符一样的宽度和高度。如果按照正常字符显示,显然会覆盖掉上有一行字符。为了避免这种情形的发生,我们需要将窗口的背景模式由默认的不透明模式(OPAQUE)设置为透明模式(TRANSPARENT)。待绘制完下划线之后,再恢复原有的不透明模式。例如:
SetBkMode (hdc, TRANSPARENT);//将窗口背景模式设置为透明模式。
SetBkMode (hdc, OPAQUE);//将窗口背景模式设置为不透明模式。
SetBkMode函数用于设置设备上下文(Device Context)中的背景模式。
SetBkMode函数的原型:
int SetBkMode(
HDC hdc, // 设备上下文句柄
int mode // 背景模式
);
背景模式定义了绘制操作的背景处理方式。通过设置背景模式,可以控制绘制操作是否填充背景色以及如何处理背景。
如果背景模式设置为TRANSPARENT,绘制操作将不填充背景色,可以实现绘制透明的效果。这在绘制文本时常用,使得文本绘制时只覆盖前景部分,而不影响背景。
如果背景模式设置为OPAQUE,绘制操作将使用背景色填充背景,确保绘制的内容在背景上完全不透明。
需要注意的是,SetBkMode函数只设置背景模式,并不会立即对设备上下文中的现有绘图进行影响。绘图操作实际上是在调用绘图函数(如TextOut、Rectangle等)时应用背景模式。
■设置绘图模式
在Windows编程中,绘图模式通常指的是一种指导如何进行绘图操作的模式或策略。在许多不同的绘图操作中,例如绘制线条、填充形状或者显示文本,都存在着各自的绘图模式。以下是一些主要的绘图模式:
1.背景模式(Background Mode):可以使用SetBkMode函数设置。当背景模式设置为OPAQUE时,绘制文本时会使用当前的背景色来填充文本的背景。而当背景模式设置为TRANSPARENT时,绘制文本时不会修改文本的背景。
2.映射模式(Mapping Mode):控制用于设备上下文的坐标系统如何映射到物理设备,比如位图或打印机等。可用的映射模式包括MM_TEXT(逻辑单位等于物理单位)、MM_LOMETRIC、MM_HIMETRIC(使用公制单位)、MM_LOENGLISH、MM_HIENGLISH(使用英制单位)等。以MM_TEXT为例,此模式下,逻辑单位和实际的像素一一对应。我们将在4.5节详细讲解Windows系统的映射机制。
3.混合模式(ROP2 Mode):控制如何通过二元光栅操作(binary raster operation)来混合源颜色和目标颜色。例如,R2_COPYPEN只绘制源颜色,R2_MASKPEN只在源色和目标色相同的地方绘制目标色等。
混合模式(“Rop” means “Raster OPeration”)可以使用SetROP2函数进行设置。该函数的原型如下:
int SetROP2(
HDC hdc,
int rop2
);
其中,参数hdc是设备上下文的句柄,参数rop2则是指定设置的混合模式。可用的混合模式包括以下几种:
R2_BLACK:总是返回黑色。
R2_COPYPEN:源色覆盖目标色。
R2_MASKNOTPEN:源色与目标色的反色逻辑与。
R2_MASKPEN:源色与目标色的逻辑与。
R2_MASKPENNOT:源色与目标色的逆逻辑与。
R2_MERGENOTPEN:源色与目标色的反色逻辑或。
R2_MERGEPEN:源色与目标色的逻辑或。
R2_MERGEPENNOT:源色与目标色的反色逻辑或。
R2_NOP:忽略源色。
R2_NOT:目标色的反色。
R2_NOTCOPYPEN:源色的反色。
R2_NOTMASKPEN:源色或目标色的反色逻辑与。
R2_NOTMERGEPEN:源色或目标色的反色逻辑或。
R2_NOTXORPEN:源色或目标色的反色逻辑异或。
R2_WHITE:总是返回白色。
R2_XORPEN:源色与目标色的逻辑异或。
这些模式定义了在进行像素绘制操作时源色与目标色的组合方式,可以精确地控制如何渲染新色。例如,R2_COPYPEN模式将简单的用源色覆盖目标色,而R2_XORPEN模式则会用源色与目标色的逻辑异或结果作为新色。这个功能可以使开发者在进行绘图操作时能获得不同的视觉效果或者实现特殊的视觉需求。
如果我们想获取当前绘图模式:iDrawMode = GetR0P2 (hdc);
Windows提供了一个名为GetROP2的宏,用于获取设备上下文(Device Context)中的当前布尔模式(Raster Mode)。
该宏的使用方式如下:
int GetROP2(
HDC hdc // 设备上下文句柄
);
GetROP2宏用于获取当前设备上下文中的布尔模式。布尔模式定义了绘制操作的像素合并规则,即如何将新的绘制操作与已有的图像进行合并。返回值为整型,表示当前的布尔模式。
需要注意的是,GetROP2宏返回的是一个整型值,而不是一个函数。因此,调用GetROP2宏时不需要使用函数调用的语法,而是直接使用宏的名称。
举例
下面是一个示例代码,演示如何使用GetROP2宏获取当前设备上下文的布尔模式:
HDC hdc = GetDC(hwnd); // 获取窗口的设备上下文
int rop2 = GetROP2(hdc); // 获取当前布尔模式
ReleaseDC(hwnd, hdc); // 释放设备上下文
绘图模式可以帮助开发者详细控制绘图操作的行为,根据需要选择适当的模式可以实现更为复杂和精细的绘图效果。例如,改变背景模式,可以控制是否填充文本背景;而改变映射模式,则可以更好地控制如何在不同大小和分辨率的设备上进行绘图。