WIN32汇编语言教程:第07章 图形操作 · 7.4 块传送操作(1)
除了7.2小节中的绘图函数,块传送函数也是重要的图形操作函数。块传送指把源位置中的数据块按照指定的方式传送到目的位置中。把内存中的位图复制到窗口客户区以及在不同的DC中复制图形数据都要用到块传送操作,块传送完成的工作就相当于图形之间的拷贝工作。块传送函数有PatBlt,BitBlt,MaskBlt,PlgBlt,TransparentBlt和StretchBlt等。
7.4.1 块传送方式
与7.2.4小节中介绍的绘图函数的ROP操作类似,块传送函数也是可以用ROP码来定义的传送方式,但块传送函数的ROP码定义不同于7.2.4小节中的ROP码,因为这里涉及的操作对象更多。
块传送的ROP码是一个32位的整数,对应的操作涉及3种原始数据:源像素、目标像素和画刷,块传送操作的结果是目标像素的数据被3种原始数据的计算结果替换掉。
并不是每一种ROP码都要用到全部3种原始数据,有的甚至连1种也用不到,例如全黑或者全白的ROP码。块传送函数使用的ROP码总共有256种,它们是3种原始数据进行不同位操作(取反、与、或和异或)的组合,但有些ROP码对应的操作结果实在是太难想像了,比如ROP码0e20746对应的操作是((目标像素 xor 画刷) and 源像素) xor 目标像素),凭这个算式的确比较难以想像最后得到的位图是什么样子的!在实际使用中很多算法组合也并不是那么有用,所以Windows只对15种最常用的ROP码定义了预定义的助记代码,
如表7.6所示,对于这些ROP码,在程序中可以直接使用助记码,对于表中没有列出的ROP码,可以直接使用16进制数值。
表7.6 块传送函数中使用的ROP码
ROP码 | 16进制数值 | 新像素点算法 |
BLACKNESS | 00000042h | 全部为0 |
DSTINVERT | 00550009h | not 目标像素 |
MERGECOPY | 00c000cah | 画刷 and 源像素 |
MERGEPAINT | 00bb0226h | (not 源像素)or 目标像素 |
NOTSRCCOPY | 00330008h | not 源像素 |
NOTSRCERASE | 001100a6h | not(源像素 or 目标像素) |
PATCOPY | 00f00021h | 画刷 |
PATINVERT | 005a0049h | 画刷 xor 目标像素 |
PATPAINT | 00fb0a09h | 画刷 or (not 源像素)or 目标像素 |
SRCAND | 008800c6h | 源像素 and 目标像素 |
SRCCOPY | 00cc0020h | 源像素 |
SRCERASE | 00440328h | 源像素 and(not 目标像素) |
SRCINVERT | 00660046h | 源像素 xor 目标像素 |
SRCPAINT | 00ee0086h | 源像素 or 目标像素 |
WHITENESS | 00ff0062h | 全部为1 |
7.4.2 块传送函数
1. PatBlt函数
PatBlt函数完成的是“图案块传送”的功能,即“PatternBlockTransfer”。使用的方法是:
invoke PatBlt,hDC,xDest,yDest,dwWidth,dwHeight,dwROP
这个函数将当前画刷的图案拷贝到hDC中以(xDest,yDest)为左上角坐标,dwWidth为宽度,dwHeigth为高度的区域中,传送的方式由dwROP中的ROP码指定。PatBlt函数的功能和矩形填充函数FillRect与InvertRect等是很像的,但它包含了它们的全部功能,如ROP码指定DSTINVERT,那么PatBlt的功能就相当于InvertRect函数;ROP码指定为PATCOPY的时候,PatBlt的功能相当于FillRect函数。
在BmpClock.asm的_CreateBackGround子程序中,当建立背景图片的时候,就是用PATCOPY方式的PatBlt函数用资源中的背景图片填充整个时钟背景的。
在所有的ROP码中,可以用在PatBlt函数中的只有一部分,所有算法中包含源像素的ROP码在PatBlt函数中都不能使用,因为PatBlt函数只涉及当前画刷和目标像素,并没有一个“源像素”,所以对于这个函数来说,表7.6中的ROP码中只有BLACKNESS,WHITENESS,DSTINVERT,PATINVERT和PATCOPY是可用的。
2. BitBlt函数
PatBlt函数能完成的工作BitBlt函数都能完成,BitBlt是“数据块传送”的意思,即“Bit BlockTransfer”。BitBlt函数的用法是:
invoke BitBlt,hDcDest,xDest,yDest,dwWidth,dwHeigt,hDcSrc,xSrc,ySrc,dwROP
这个函数将源hDcSrc中以(xSrc,ySrc)为左上角的一个矩形区域传送到目标hDcDest中以(xDest,yDest)为左上角的地方去,矩形的宽度为dwWidth,高度为dwHeight,当然目标DC中的最后结果是由dwROP中的ROP码定义的源、目标和画刷三者数据的组合。
灵活使用ROP码可以实现很多的功能,比如在一个背景图片上叠加一个非矩形的位图,游戏程序中人物在背景上面的移动就是这样的一个例子。BmpClock程序中也实现了类似的功能——读者可以注意到,程序可以自由更换背景和边框,但是边框是中空的,它相当于以一个不规则的图形叠加在背景上面,图7.10示范了实现的方法。
分析一下BmpClock.asm中的_CreateBackGround子程序就可以发现,程序用到了资源中的Back1.bmp,Mask1.bmp和Circle1.bmp这3个图片(在图7.10中以A,B,C来表示),子程序将3个图片装入内存后,创建了3个DC来存取它们,对应的DC句柄分别放在@hBmpBack,@hBmpMask和@hBmpCircle中。
好了!现在的任务是将图片C中需要的部分(非黑色部分)透明叠放在以图片A形成的背景上,得到时钟背景图片D。继续做准备工作:为图片D建立一个未初始化位图和内存DC,DC句柄存放在hDcBack中。
图7.10 在背景上叠加不规则图形的方法
如图7.10中的步骤1所示,首先,程序用CreatePatternBrush建立以图片A为图案的画刷,用PatBlt函数以这个画刷填充整个图片:
invoke CreatePatternBrush,@hBmpBack push eax invoke SelectObject,hDcBack,eax invoke PatBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,PATCOPY pop eax invoke DeleteObject,eax
现在如果直接将图片C拷贝上去,虽然需要的部分是拷贝上去了,但是图片C中的黑色部分也会覆盖全部的背景像素,为了让黑色部分的背景像素保持不变,应该使用or操作,因为黑色的颜色值为0,任何数据和0进行or操作将保持不变,查看ROP码可以发现,SRCPAINT使用的是or操作,所以可以使用SRCPAINT操作码进行BitBlt操作。
但还有个问题:图片C中的非黑色部分如果也用or操作绘画到背景上,那么经过和背景像素的or操作后就不是原来的颜色值了,为了让这部分不变,解决的办法是预先将背景中对应的部分先绘制成黑色,这样对应图片C中的黑色部分将保持背景颜色,而非黑色部分将使用图片C中的像素。遮掩图片B就是这样用的,它是一个黑白两色的图片,黑色部分是图片C中要在背景上“镂空”的部分,在步骤2中,程序使用下列语句将图片B用SRCAND操作码绘制到背景上:
invoke BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcMask,0,0,SRCAND
查表7.6可以发现,SRCAND进行源像素和目标像素的and操作,任何数和1进行and将保持不变,和0进行and将变成0,这样背景中对应图片B中的白色部分将保持不变,对应图片B中的黑色部分将被“镂空”。
接下来就是最后的步骤3了:
invoke BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcCircle,0,0,SRCPAINT