📄 007.txt
字号:
预 定 义 值
说 明
BLACK_PEN
WHITE_PEN
NULL_PEN
BLACK_BRUSH
DKGRAY_BRUSH
GRAY_BRUSH
LTGRAY_BRUSH
WHITE_BRUSH
HOLLOW_BRUSH或NULL_BRUSH
ANSI_FIXED_FONT
ANSI_VAR_FONT
DEFAULT_GUI_FONT(Win95)
OEM_FIXED_FONT
SYSTEM_FONT
DEFAULT_PALETTE
黑色画笔
白色画笔
空画笔
黑色画刷
深灰色画刷
灰色画刷
浅灰色画刷
白色画刷
空画刷
等宽系统字体
不等宽系统字体
默认系统字体(用于菜单、对话框等)
OEM等宽字体
默认系统字体(用于菜单、对话框等)
默认图案
NULL_PEN和NULL_BRUSH是空画笔和空画刷,之所以有空的对象,是因为绘制填充区域的函数同时用到了画笔和画刷——绘制的外框使用当前画笔,中间用当前画刷填充。使用空对象可以有机会画出没有边框线只有填充图案,或者只有边框线而不填充的区域来。
用GetStockObject函数得到对象句柄以后,就可以用SelectObject函数将对象句柄设置到DC中了。例子文件Clock.asm中的_ShowTime函数中用GetStockObject函数获取了一个BLACK_BRUSH画刷,用来绘画时钟的刻度。
2. 使用自定义的画笔和画刷
使用GetStockObject函数得到的对象是最“简陋”的,如画笔只能是白色或黑色的宽度为1像素的实线,画刷只能是白色、黑色和有限的几种灰色色块。要想使用彩色的、多种多样风格的画笔和画刷,就必须用自定义的方法。
创建自定义的画笔可以使用CreatePen,ExtCreatePen或CreatePenIndirect函数,CreatePen函数的使用方法是:
invoke CreatePen,fnPenStyle,dwWidth,dwColor
mov hPen,eax
fnPenStyle参数是画笔风格,它可以是两种实线风格PS_SOLID,PS_INSIDEFRAME或空画笔PS_NULL,以及几种虚线风格PS_DASH,PS_DOT,PS_DASHDOT或PS_DASHDOTDOT。它们对应的线条如图7.6所示,图中从上到下分别是PS_SOLID,PS_DASH,PS_DOT,PS_DASHDOT,PS_DASHDOTDOT和PS_INSIDEFRAME风格的线条,几种虚线的风格很好记,只要记得“点”就是DOT,“划”就是DASH就可以了,如PS_DASHDOTDOT风格就是由“划、点、点”重复组成的虚线。
PS_SOLID和PS_INSIDEFRAME风格的画笔使用的都是实线线条,它们之间的区别在于当画笔的宽度大于1像素。在使用区域绘画函数的时候,PS_SOLID线条会居中画于边线上,而PS_INSIDEFRAME线条会全部画在边线里面,它的宽度会向区域的内部扩展,所以它的名称是InsideFrame。
图7.6 几种自定义画笔风格
CreatePen 函数的dwWidth参数定义了画笔的宽度,单位是DC坐标映射方法中定义的逻辑单位,如果这个参数使用NULL,那么函数会使用1像素的宽度。宽度参数会影响到风格参数:当宽度大于1的时候,画笔风格不能使用虚线,这时候即使指定了虚线风格,函数也会自动使用PS_SOLID风格。dwColor参数指定了画笔的颜色。
例子源代码的_ShowTime子程序中用不同宽度的线条来绘画时、分、秒指针,绘画前就使用CreatePen函数创建了不同宽度的画笔。
如果需要创建更复杂的画笔,可以使用ExtCreatePen函数。这个函数除了有CreatePen的全部功能外,还可以让用户自己定义线条的样子,这样可以不必限制于上面的点点划划了。函数的用法读者可以参考函数手册。
创建自定义画刷可以使用的函数有:CreateSolidBrush,CreateHatchBrush,CreatePatternBrush和CreateBrushIndirect。
CreateSolidBrush创建单色的画刷:
invoke CreateSolidBrush,dwColor
mov hBrush,eax
要输入的惟一参数是画刷的颜色。而CreateHatchBrush可以创建几种预定义图案的画刷:
invoke CreateHatchBrush,iHatchStyle,dwColor
mov hBrush,eax
dwColor指定了图案线条的颜色,iHatchStyle定义了不同的图案线条,这些图案线条实际上是以8×8的位图重复铺开组成的,iHatchStyle的定义值可以是HS_BDIAGONAL,HS_CROSS,HS_DIAGCROSS,HS_FDIAGONAL,HS_HORIZONTAL和HS_VERTICAL,这6种图案的花样在图7.7中从左到右排列显示。
图7.7 CreateSolidBrush中的画刷图案
如果这些简单的图案不能满足使用要求,CreatePatternBrush是个很好的选择:
invoke CreatePatternBrush,hBitmap
mov hBrush,eax
这个函数用一个位图当做画刷的图案,当要绘画的区域大于位图尺寸的时候,位图被重复铺开,就像HTML文件中的背景图案一样。读者可以尝试一下用一幅做网页文件背景的位图创建一个位图画刷,并且在RegisterClassEx时在WNDCLASSEX结构中的hbrBackground字段中使用这个画刷,这样创建出来的窗口背景会和网页背景一样华丽!读者可以参考所附光盘的Chapter07\TestObject目录中的源代码。
对于自定义的画笔和画刷,还有其他自定义的对象,在不再需要的时候必须使用DeleteObject函数删除,但是要注意:当对象还是一个DC的当前对象的时候不要将它删除,在删除前应该确定DC中已经选入了其他的对象。与之相反,用GetStockObject获取的预定义对象使用后不需要删除,但是对它们调用DeleteObject也没有关系,因为它们不会被真正删除。由于SelectObject返回值就是DC原来使用的对象句柄,所以删除对象的一个好时机就是当SelectObject返回的时候,如例子程序的_ShowTime子程序中用的:
invoke CreatePen,PS_SOLID,2,0
invoke SelectObject,_hDC,eax
invoke DeleteObject,eax
SelectObject将CreatePen创建的画笔句柄选入DC,返回值eax就是以前使用的画笔句柄,这个句柄不再使用了,所以可以在下面用DeleteObject直接删除,而这次建立的画笔可以在下次执行SelectObject后用同样的方法删除。
7.2.2 绘制像素点
在DC上绘制像素点是绘图最基本的操作,使用的方法是:
invoke SetPixel,hDC,dwX,dwY,dwColor
SetPixel函数在hDC的dwX、dwY位置以dwColor为颜色画上一个像素点,如果需要获取hDC中某个像素点当前的颜色值,那么可以使用GetPixel函数:
invoke GetPixel,hDC,dwX,dwY
mov dwColor,eax
虽然绘画像素是最基本的绘图操作方法,但是在程序中一般很少使用SetPixel函数,因为它的开销太大了,只适合用在需要少量绘画像素的地方,如果要绘画一个线条或者整个区域,那么最好使用画线函数或者填充函数,因为这些函数是在驱动程序级别上完成的,所有的硬件加速功能都可以用上。
图形处理前最基本的步骤是获取像素,但也不应该用GetPixel函数来获取一大块的像素数据,理由是同样的。如果要分析整个区域的像素数据,最好的办法就是用GetDIBits函数将全部数据拷贝到内存中再进行处理。
7.2.3 绘制图形
GDI的图形绘制函数主要有绘制线条和填充区域两大类。绘制线条的函数以当前画笔绘制线条;绘制填充区域的函数以当前画笔绘制边线,并以当前画刷填充中间的区域。
1. 绘制线条
绘制线条的函数有画直线的LineTo,画多条直线的Polyline和PolylineTo,画贝塞儿曲线的PolyBezier和PolyBezierTo,画弧线的Arc和ArcTo。
DC的数据结构中有一个“当前点”,LineTo函数就是从当前点画一条直线到参数中指定的点,并把参数中指定的点设置为新的当前点。画线函数中所有以To结尾的函数都是从当前点开始绘画的,如LineTo,PolylineTo,PolyBezierTo和ArcTo,这些函数在绘画结束后会把绘制的最后一点设置为新的当前点,所以在使用这些函数的时候要考虑到当前点也是参与绘制的坐标一部分。而其余的Polyline,PolyBezier和Arc函数则和当前点没有关系,也不会影响当前点的位置。
如果要设置当前点的位置,可以使用MoveToEx函数:
invoke MoveToEx,hDC,dwX,dwY,lpPoint
dwX和dwY指出了新的当前点的坐标,lpPoint指向一个空的POINT结构,用来返回原来的当前点位置,如果不需要的话,这个参数可以使用NULL。
另一个函数也可以得到当前点的坐标:
invoke GetCurrentPositionEx,hDC,lpPoint
同样,lpPoint指向一个用来返回当前点坐标的POINT结构地址。
如果要绘制一条直线,必须配合使用MoveToEx和LineTo函数,首先由MoveToEx函数设置一个当前点当做起始坐标,然后用LineTo绘画到结束坐标,如Clock.asm中的_DrawLine子程序中就是这样绘制时钟指针的:
invoke MoveToEx,_hDC,@dwX1,@dwY1,NULL
invoke LineTo,_hDC,@dwX2,@dwY2
这两句代码绘画一条从@dwX1,@dwY1到@dwX2,@dwY2的直线。
如果要绘制是相连的多条直线,可以使用Polyline或PolylineTo函数:
invoke PolylineTo,hDC,lpPoint,cPoints
invoke Polyline,hDC,lpPoint,cPoints
lpPoint指向一个包含一系列POINT结构的缓冲区,由于POINT结构只有X和Y两个字段,所以缓冲区中的数据实际上是x1,y1,x2,y2,x3,y3,…,cPoints参数指出了点的数目,注意:PolylineTo画出的直线是从当前点坐标(x,y)开始,然后到(x1,y1),再到(x2,y2),…,而Polyline函数画出的直线是从(x1,y1)开始的,对于这个函数,如果cPoints参数指定了n个点,那么直线的数量实际上是n?1。当绘制的相连直线很多的时候,用Polyline或PolylineTo比多次使用LineTo的速度要快很多,就像用填充函数比多次使用SetPixel要快一样。
表7.3举例说明了这些画线函数的功能,表中的(x1,y1)或(x2,y2)等表示点1或点2的坐标,(xc,yc)表示当前点的坐标,当前点在图中用c表示。
表7.3 画线函数的功能
函 数
说 明
图 例
LineTo(hDC,x,y)
从当前点到(x,y)点
PolylineTo(hDC,lpPoint,5)
lpPoint指向存放(x1,y1)到(x5,y5)的缓存区,函数画的线条从(xc,yc)到(x1,y1)到(x2,y2)…到(x5,y5),共5条直线
Polyline(hDC,lpPoint,5)
lpPoint指向存放(x1,y1)到x5、y5的缓存区,函数画的线条从(x1,y1)到(x2,y2)…到(x5,y5),共4条直线
PolyBezierTo(hDC,lpPoint,3)
绘画的Bezier曲线的控制点为(xc,yc)和(x1,y1)和(x2,y2)和(x3,y3)
PolyBezier(hDC,lpPoint,4)
绘画的Bezier曲线的控制点为(x1,y1)和(x2,y2)和(x3,y3)和(x4,y4)
ArcTo(hDC,x1,y1,x2,y2,
x3,y3,x4,y4)
首先画(xc,yc)到起始点的直线,再画起始点到结束点的弧线
Arc(hDC,x1,y1,x2,y2,
x3,y3,x4,y4)
画起始点到结束点的弧线
对于Arc和ArcTo函数,参数(x1,y1)和(x2,y2)定义了一个矩形的对角点,然后在和这个矩形相切的椭圆上面,以椭圆的中心(也就是矩形的中心)画两条假想的直线到(x3,y3)和(x4,y4),这两条直线和椭圆相交的点就是圆弧的起始点和结束点。在默认情况下,圆弧由起始点沿着椭圆从逆时针方向画到结束点。不过绘画方向可以由SetArcDirection函数重新规定:
invoke SetArcDirection,hDC,AD_COUNTERCLOCKWISE ;逆时针方向
invoke SetArcDirection,hDC,AD_CLOCKWISE ;顺时针方向
读者一定注意到了一个问题:在画线的时候,如果当前的画笔是虚线的话,虚线的不连续部分实际上是由白色组成的,当虚线画在非白色的背景上的时候这一点显得特别明显。实际上,可以选择这些不连续部分的颜色,用以下的语句就可以做到这一点:
invoke SetBkColor,hDC,dwColor
调用后不连续的部分就将用dwColor指定的颜色绘画。
但是改变颜色也并不是惟一的选择,GDI允许这部分并不绘画任何颜色,也就是可以是“透明”的,用下面的调用可以将模式在透明和非透明之间切换:
invoke SetBkMode,hDC,OPAQUE ;非透明模式
invoke SetBkMode,hDC,TRANSPARENT ;透明模式
两种模式以及绘画颜色不单影响虚线的空隙部分,同样也影响CreateHatchBrush函数创建的画刷,因为这种画刷使用几种由线条构成的图案,当用这种画刷填充一个区域的时候,线条图案的空隙部分同样受SetBkColor函数和SetBkMode函数的影响。
2. 绘制边界框和填充区域
绘制边界框和填充区域其实是同一件事情。如果当前画笔是NULL_PEN的话,画出来的是没有边线的填充区域;如果当前画刷是NULL_BRUSH的话,那么只有边线而不会填充;如果当前画刷既不是NULL_PEN也不是NULL_BRUSH,那么画出来的图形既有边线也是填充的。
绘制区域的函数有画矩形的Rectangle,画圆角矩形的RoundRect,画多边形的Polygon,画弦的Chord,画圆饼的Pie和画椭圆的Ellipse。这些函数的使用效果见如7.4所示。
表7.4 填充函数的功能
函 数
说 明
图 例
Rectangle(hDC,x1,y1,x2,y2)
画以(x1,y1)和(x2,y2)为对角坐标的填充矩形
RoundRect(hDC,x1,y1,x2,y2,w,h)
画以(x1,y1)和(x2,y2)为对角坐标的填充矩形,四个角以一个小椭圆来画圆角,小椭圆的宽和高为w和h
函 数
说 明
图 例
Polygon(hDC,lpPoint,5)
lpPoint指向存放(x1,y1)到(x5,y5)的缓存区,函数从(x1,y1)到(x2,y2)…到(x5,y5),再回到(x1,y1),一共画5条直线并填充
Chord(hDC,x1,y1,x2,y2,
x3,y3,x4,y4)
以和Arc函数同样的方法画弧,然后连接弧的两个端点并填充
Pie(hDC,x1,y1,x2,y2,
x3,y3,x4,y4)
以和Arc函数同样的方法画弧,然后将弧的两个端点分别和椭圆中心连接并填充
Ellipse(hDC,x1,y1,x2,y2)
以(x1,y1)和(x2,y2)为对角定义一个矩形,然后画矩形相切的椭圆并填充
在这些函数中,Polygon的调用方式和Polyline很相似,只不过如果最后一点和第一点不同的话,函数自动再画一条和起始点相连的直线将整个区域闭合起来。用Polygon绘画的多边形中各条直线可能相交,Windows允许程序自行选择填充的模式,可以是表7.4中Polygon一栏中的上面那个图例(填充全部区域),也可以是下面那个图例(间隔填充区域)。可以用下面的函数切换填充的模式:
7.2 绘 制 图 形(6)
invoke SetPolyFillMode,_hDC,ALTERNATE ;间隔填充
invoke SetPolyFillMode,_hDC,WINDING ;填充全部区域
Chord函数和Pie函数的参数使用和画弧线的Arc函数相似,只不过Chord函数将弧线的两端直接相连,形成一个“弦”,而Pie函数将两端和圆心相连,形成一个“圆饼”,这两个函数绘画的方向同样受SetArcDirection函数设置的影响。
在例子Clock.asm中,程序在_DrawDot子程序中用Ellipse函数绘画时钟的刻度,读者也可以将程序改动一下,尝试着用Polygon画五角星来当做时钟的刻度。
除了这些函数,还有3个和矩形有关的填充函数:FillRect,FrameRect和InvertRect,这些函数不使用当前画笔画边线,也不用当前画刷填充,其中FillRect函数用指定的画刷hBrush填充一个lpRect指定的矩形区域,lpRect指向一个RECT结构;FrameRect函数用指定画刷hBrush绘画边线;InvertRect函数将lpRect指定的矩形区域中的颜色值取反。用法如下。
invoke FillRect,hDC,lpRect,hBrush
invoke FrameRect,hDC,lpRect,hBrush
invoke InvertRect,hDC,lpRect
假设背景为白色,而参数中hBrush指定的画刷为灰色画刷,那么上述3个函数的运行结果如图7.8所示。
图7.8 FillRect,FrameRect和InvertRect函数的运行结果
图中左边是FillRect的运行结果,可以看到图案没有边线;中间是FrameRect的运行结果,它用灰色画刷绘画边线,得到了一个灰色的矩形边框;右边是InvertRect的运行结果,由于底色是白色的,白色取反得到的是黑色,所以整个矩形都变成了黑色。
7.2.4 绘图模式
在前面的内容中我们都是尝试在DC上用绘图函数画出需要的图形,对于DC上被绘画上去的像素来说,相当于用画笔(或画刷)的像素点代替了原来的像素点,但Windows也可以用
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -