Ron Gery
Microsoft 网络开发技术小组
摘要
这篇文章讨论了在 Microsoft Windows 图形环境中用位图达到透明和屏蔽效果的几种方法,包括通过仿真和使用特殊的驱动器功能。包含其中的一个小样本应用程序 TRANSBLT 详细阐明了这篇文章讨论的大多数方法。
介绍
使用透明(TRANSPARENT)背景模式(用SetBrMode函数设置),一个应用程序就可以用透明文本,透明风格的线条和透明形状的刷子。令人悲伤的是,Microsoft Windows图形环境并没有为透明位图提供一个简单的接口。(是的,它提供了,但是并没有对它进行广泛的支持,就象在下文中“容易的位图透明性”中提到的)。幸运的是,通过使用一个屏蔽位图和几次调用具有经过仔细选择的光栅操作的BitBlt,一个应用出现可以模仿这种效果。
到底什么是透明的位图呢?它是一个位图,通过它目的文件的一部分仍然可以看得见。一个简单的例子就是类似于控制面板图象等基于Windows的图象。控制面板图象本身基本上是个长方形。当它被最小化时,通过这个长方形图象位图的部分可以看见桌面。从理想化的角度讲,这图象位图被设计成长方形其中有些象素被指定为透明的以至于当位图被使用,那些象素就不会挡住目的文件。透明的位图可以通过移动的、非矩形图象变得更为有趣。下面将要描述得模仿方式可以用来完成这些透明效果。
符号
这篇文章使用透明和不透明这两个词来描绘源位图中得象素。透明象素是那些不会影响目标文件的象素。不透明象素是那些画在目标文件上并取代该位置上原来的东西的象素。
白色和黑色分别被假定为全1和全0的值。这在所有已知的Windows显示驱动器上都是正确的,包括调色板设备。
基本的操作涉及到从源文件到目标文件 的块传递,额外的与单色屏蔽有关的块传递也是需要的。源文件和目标文件由他们的设备上下文代表hdcSrc和hdcDest即可以是位图也可以是设备表面本身。有hdcMask提到的屏蔽被假定为被选进兼容DC的单色位图。
背景概念
在讨论实际的透明模仿之前,我们应该定义和复习一些基本图形概念。
光栅操作
BitBlt函数的最后一个参数指定了一个光栅操作(ROP),它明确定义了如何将源文件、目标文件和模式(由现在选出的刷子画笔定义)的位组合去形成一个目标文件。因为一个位图只是一个位值的集合,光栅操作(ROP)只是一个在位上操作的布尔等式。相应使用的设备,位图中的代表不同的事物。
- 在多色设备上,每个象素由一个位集合代表,他们要么形成一个指向颜色的索引,要么直接代表一种颜色。
- 在单色设备上,每个象素由一个位来代表,0表示黑色并且1代表白色。
对于所有的设备类型,光栅操作(ROP)只简单地在位展示上进行而不考虑他们的实际意义。
有一个技巧,就是用一种有意义的方式合并位。在Windows3.1版软件开发包中的程序员参考,第三页:消息、结构和宏中的附录A列举了256种可能的三重光栅操作(ROP)。光栅操作(ROP)提供了多种合并位图数据的方法,而且你经常可以使用不只一种的方法得到你想要的效果。这篇文章只讨论其中的四种。
预先定义的名字 | 布尔操作 | 透明仿真中的用途 |
SRCCOPY | src | 直接将源拷贝到目的 |
SRCAND | src AND dest | 将目标文件中对应于源文件黑色区域的部分变黑,将对应于白色区域的部分留着不动 |
SRCINVERT | src XOR dest | 将源插入到目标。二次使用时,将目标恢复到它原来的状态。在某种条件下可以代替SRCPAINT 操作 |
SRCPAINT | src OR dest | 将源文件中的非白色区域刷到目标文件中。源中的黑色区域不转换到目标中。 |
一些打印机不支持某些光栅操作,尤其是那些涉及到目标文件的光栅操作。因为这一点,这篇文章中描写的技巧特地以显示为目的而且有可能在某些打印机设备上不能工作,比如PostScript打印机。
透明屏蔽
在这篇文章中,“面具”一词不是指蝙蝠侠戴在脸上的东西。它指的是一个限制其他位图可见部分的一个位图。“面具”有两个控件:不透明部分(黑色),在这一部分源位图是可见的和透明部分(黑色),在这一部分目标文件保持未动。因为“面具”只由两种颜色 组成,所以它可以很方便地由一种单色位图代表,虽然它可以是一个黑白色位图 。就象在下面的“真正的屏蔽方法”和“使源文件变黑的方法”中要讨论的,数据块传递屏蔽被用作多数数据块传递处理的一部分,它的源位图的最终透明数据块传递设置目标文件。TRANSBLT样本应用程序使用带设置为1的透明色素和设置为0的非透明色素的单色屏蔽。如果需要应用程序可以转换这两个值,并将在这一部分中的一些将要描述的单色到彩色的转换中进行补充。
除了为透明性提供方便以外,屏蔽在模仿复杂的剪裁操作时也很有用。这种剪裁操作不能被有效地在使用区域中处理。一个被屏蔽的数据块传递的网状效应将裁减掉源位图的一部分。比如:要只显示位图中的一个圆形区域,可创建一个象源文件一样大小的屏蔽并且在相应的区域画一个透明位的圆形。执行这一被屏蔽的数据块传递的机制将在后面的“真正的屏蔽方法”和“使源文件变黑的方法”中描述。
单色到 彩色的转换
透明仿真也牵涉到基于Windows的单色位图到彩色位图的转换机制。反之亦然。基于Windows的文本前景色和背景色的概念被用于在两种格式之间进行转换。在对一彩色目标文件进行位传递操作的时候,一个单色的源位图(和/或当可应用时一个画刷)在实际的光栅操作(ROP)中在这一位上实现之前被转换成背景色。相反的,当目标文件是单色时,Windows 把彩色源转换成单色。在这种情况下,彩色位图中所有和背景色一致的象素都变成1,其他的象素都被转换成0。因为下面所要涉及到的例子都使用单色屏蔽,所以对一个应用程序而言,在执行块操作之前正确地设置背景色和前景色是非常重要的。
性能和屏幕闪烁
增强的位图操作变得比较慢的原因完全是因为被影响的位的数目。而且当直接对屏幕进行操作导致闪烁的事实又使得这一点更为严重。当被影响的区域增大尺寸时,事情只会变得更加糟糕,虽然没有什么办法可以魔术般的提高速度,但是可以通过使用阴影位图来删除可见的闪烁 。首先,应用程序把将要被影响的屏幕区域复制到存储位图,然后应用程序在阴影位图而不是在屏幕上实现位操作(例如,透明效果 )。最后,阴影又被复制到屏幕上。结果只有一个块传递影响屏幕,所以闪烁没有了。很明显,两个额外的块操作引起了速度减慢(虽然在有些设备上存储器块传递能比已经访问了屏幕的块传递要块一些),但是依靠位图的尺寸和惊奇的操作,操作可能会因为闪烁消失而让人感觉到快了一些。事物也因为没有让人混乱的闪烁而变得更为清楚。阴影操作是否恰当取决于应用程序特定的需要。
真正的屏蔽块传递并不需要对即将有用的源位图的某部分做任何的修改。被屏蔽的块传递涉及到了步操作并且屏蔽把所有透明的元素都设置为1,所以不透明的元素设置为0。下面是有关基本代码:
// Set up destination for monochrome blt (only needed for monochrome
// mask). These are the default values and may not need to be
// changed. They should also be restored.
SetBkColor(hdcDest, RGB(255, 255, 255)); // 1s --> 0xFFFFFF
SetTextColor(hdcDest, RGB(0, 0, 0)); // 0s --> 0x000000
// Do the real work.
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCINVERT);
BitBlt(hdcDest, x, y, dx, dy, hdcMask, 0, 0, SRCAND);
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCINVERT);
被屏蔽的块传递处理中的三步操作如下:
- 第一步(带SRCINVERT的位块传递)将源位图异或到目标文件。这看起来有点意思,但第二步异或有把目标文件恢复成原始状态的效果。
- 第二步(带SRCAND的位块传递)是一个屏蔽操作。当屏蔽与目标文件相与,所有的透明象素都不会改变目标文件的象素,而不透明象素则直接把目标文件变为黑。现在目标文件中有了一个源文件的不透明部分给勾勒出来的图象,而它自身在透明部分中的异或图象。
- 第三步(带SRCINVERT的位块传递)与源文件异或送到目标文件。透明象素被恢复成源状态(两步异或就能做到),不透明象素则季节从源文件上复制。
不幸的是,当三个步骤执行时,目标文件确实有一阵看起来相当难看,而直接对屏幕执行三次块传递又会引起屏幕闪烁。
使源文件变黑的方法
只要在创建源位图时稍稍计划一下,透明性块传递就可以减少到只有两个调用。屏幕仍和上面的例子一样保持不变,但是源文件则必须在屏蔽的块传递代码看起来象这样:
// Set up destination for monochrome blt. These are the default
// values and may not need to be changed. They should also be
// restored.
SetBkColor(hdcDest, RGB(255, 255, 255)); // 1s --> 0xFFFFFF
SetTextColor(hdcDest, RGB(0, 0, 0)); // 0s --> 0x000000
// Do the real work.
BitBlt(hdcDest, x, y, dx, dy, hdcMask, 0, 0, SRCAND);
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCPAINT);
屏蔽第二次用来使不透明的象素变黑而保持剩下的不变。然后源文件再这个的向上与之相或,并在目标文件现在为黑的部分上画画。因为源文件在想要透明的地方只有黑象素,或操作使得目标文件在那些透明区域保持不变。注意SRCINVERT POP可以在第二个块传递调用时代替SRCPAINT并取得多样的效果。源屏蔽设置删除了的可能性,这是XOR不同于OR的唯一情形。
用这种方法屏幕闪烁变得不再那么引人注目,而且一旦源文件已经再正确的位置被设置为黑,透明性看起来非常好。Windows也使用这种机制在屏幕上显示图象。图标被分成两部分存储在.ICO文件中,这两部分“XOR MASK”和位图本身。因为位图和图标一样小,所以实现透明性非常顺利。
位图透明性
位图透明性通常指的是一种处理,这种处理取出一幅位图,并使位图中的一种颜色变为透明,从而当位图被块传递到屏幕时,目标文件可以通过位图的透明色被看见。一个应用程序可以通过构造一个合适的屏蔽和使用在前面“真正的屏蔽方法”和“使源文件变黑的方法”中描述过的屏蔽技术来模仿这种操作。下面的章节描述了如何为不能执行透明块传递的显示设备模仿位图透明性构造一个屏蔽。
从彩色位图中构造一个单色屏蔽相当容易,因为位块传递的内置彩色单色转换自动完成所有这些工作。目标是一个所有不透明元素都设置为0和所有透明元素都设置为1的屏蔽。把背景色设置为透明色恰好做到了这一点。没有必要设置文本前景色因为他不能用于彩色单色转换(所以的非背景色素都被设置成0),下面的代码完成了这项工作:
SetBkColor(hdcSrc, rgbTransparent);
BitBlt(hdcMask, 0, 0, dx, dy, hdcSrc, x0, y0, SRCCOPY);
代码构造了一个源文件相当于透明色时为1和其他地方为0的屏蔽。这复制了上面使用过的屏蔽。
使用屏蔽
现在是使用上面描述过的屏蔽方法的时候了。真正的屏蔽方法不要求额外的工作:创建 屏蔽并且源文件不需要处理。三个位块传递确实引起了屏幕闪烁,但现在只有三个的一个。
另一方面,使源文件变黑的方法要求对源位图做一些额外的工作来得到正确的输入方案透明位需要变黑。当然,如果透明色一开始就是黑色,那位图就已经准备好要运行了。在源文件上把透明色素涂黑非常类似于在目标文件上把不透明象素涂黑,并且在使用屏蔽时已经这样作了,如下所示:
SetBkColor(hdcSrc, RGB(0,0,0)); // 1s --> black (0x000000)
SetTextColor(hdcSrc, RGB(255,255,255)); // 0s --> white (0xFFFFFF)
BitBlt(hdcSrc, x0, y0, dx, dy, hdcMask, 0, 0, SRCAND);
现在有两个位块传递被用于透明的位块传递。
一旦现实的透明位块传递完成了,源位图应该被恢复到它的初始色:
SetBkColor(hdcSrc, rgbTransparent); // 1s --> transparent color
SetTextColor(hdcSrc, RGB(0,0,0)); // 0s --> black (0x000000)
BitBlt(hdcSrc, x0, y0, dx, dy, hdcMask, 0, 0, SRCPAINT);
因为源位图必须被更改,然后再恢复,相关的位块传递的总数是4。这使得处理变慢,但因为位块传递中的两个是在存储位图中进行而不是在屏幕上作的,相比较于真正的屏幕方法屏幕闪烁减少了。如果源位图能保持透明位被设置为黑色,则两个转换块传递可以一起被避免而且也只需要两个块传递用于操作;这事实上是动画制作必要的。
简易的位图透明性
有些设备驱动器直接支持块传递。一个驱动器指示使用CAPSI能力的CI-TRANSPARENT的能力由GetDevicePaps函数返回。一个特殊的背景模式,NEWTRANSPARENT指示后来的块传递是透明的块传递。目标文件的现在的背景色是透明色。当这个能力适用于驱动器时,基本的透明块传递可以按下面的代码执行:
// Only attempt this if device supports functionality.
if(GetDeviceCaps(hdcDest, CAPS1) & C1_TRANSPARENT)
{
// Special transparency background mode
oldMode = SetBkMode(hdcDest, NEWTRANSPARENT);
rgbBk = SetBkColor(hdcDest, rgbTransparent);
// Actual blt is a simple source copy; transparency is automatic.
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCCOPY);
SetBkColor(hdcDest, rgbBk);
SetBkMode(hdcDest, oldMode);
}
这确实使得事情变得简单了。不幸的是,目前没有多少设备驱动器支持透明的块传递那些用Windows3.1版本安装的块传递没有这个功能。这应该在不远的将来有所改变。
并且目前,WINDOWS。H并不包含对任意这些新变量的定义。取而代之的是MMSYSTEM。H文件提供定义,这个文件能Windows3.1版本的软件开发包(SDK)中找到。
带有设备独立位图(DIB)的透明性
如果源位图是设备独立位图(DIB)格式,那么,整个屏蔽处理可以通过用一个设备独立位图(DIB)当作源文件。屏蔽和对色彩表的简单操作就可以大大地获得简化了。处理过程与上面讨论的相同,处理应用程序可通过改变色彩表执行所有的彩色单色和单色彩色的转换,如下:
save a copy of the color table;
// Build the mask.
for (every color in the color table)
{
if (color == rgbTransparent)
color = white;
else
color = black;
}
// Prepare destination by blting the mask.
StretchDIBits(hdcDest, lpDIB, SRCAND); // (Yes, there are more
// parameters.)
// Now prepare "blacked out" source for the mask blt.
for (every color in the color table)
{
if (color == white) // (white from above change)
color = black;
else
color = original color from color table;
}
// Transparently blt the source.
StretchDIBits(hdcDest, lpDIB, SRCPAINT); // (Yes, there are more
// parameters.)
// To restore DIB to original state, restore original color table.
这种方法需要注意的关键之处在于只需要位图的一个备份因为它依靠色彩表转换即充当屏幕又充当源文件。不管,把设备独立位图(DIB)格式转换成设备依赖格式的额外不便仍然存在。