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”和位图本身。因为位图和图标一样小,所以实现透明性非常顺利。
位图透明性
位图透明性通常指的是一种处理,这种处理取出一幅位图,并使位图中的一种颜色变为透明,从而当位图被块传递到屏幕时,目标文件可以通
VCKBASE Online Help Journal No.8
图象合成技术
foenix
图象合成是通过对两张图片像素值的运算,以产生一张新的含有两张图片信息的图象,在多媒体编程中有广泛的应用。图象合成的一个典型的例子就是制作透明位图,在许多精灵动画的实现中都有应用。
精灵动画
实现一个简单的精灵动画,可以有以下几种方法来实现:
1.用SetPixel函数直接在屏幕上逐点画,这是最笨也是效率最低的方法,我们就不要考虑这种方法了。
2.BitBlt函数:需要为精灵图片制作一张黑白掩膜图片,然后用掩膜图片分别对背景和精灵图片进行处理,最后把处理过的精灵图片拷贝到背景图上。前后要使用三个BitBlt函数,如果为了防止屏幕闪烁,需要使用内存DC,这样就要使用五个BitBlt函数,如果图片比较大的话,处理速度是比较慢的。
3.最快的方法:直接写屏。在游戏编程中,一般都有大量的精灵,用Windows的GDI函数根本无法实现。一个很好的方法就是使用直接写屏技术,通过直接操作显存来加快显示速度,这在DOS时代很轻易的实现,由于Windows程序不能直接访问硬件,需要借助外挂环境来实现,比如Microsoft DirectX就被广泛用于游戏编程中,具体做法可以参阅DirectX编程方面的书籍,这里将不做介绍。
4.直接修改数据缓冲区。这种方法比较简单,其实现原理和直接写屏类似,速度也还可以。另外,通过修改数据缓冲区,你还可以实现一些其他的特殊效果,本文将重点对这种方法进行讲解。
BitBlt函数方法:
GDI的BitBlt函数的功能是将图形数据块从一个位置搬移到另一个位置,源和目标位图可以在同一个设备文本对象,也可以在不同的设备文本对象,函数原型如下:
BitBlt(HDC hDC,int x,int y,int cx,int cy,HDC hDCSrc,int xSrc,int ySrc,DWORD dwRop);
参数dwRop为光栅操作码,决定位图的显示方式,这里介绍三个下面画透明位图需要用到的的光栅操作码:
光栅操作码:MERGEPAINT
效果:源的反向"或上"目标(即:dest=(NOT src) OR dest)
说明:白色或上任何颜色都等于白色;黑色或上任何颜色颜色都不变
光栅操作码:NOTSRCERASE
效果:源的反向"与上"目标的反向(即:dest=(NOT src) AND (NOT dest))
说明: 黑色与上任何颜色都等于黑色;白色与上任何颜色颜色都不变
光栅操作码:SRCINVERT
效果:源与目标"异或"起来(即:dest=src XOR dest)
说明:黑色与任何颜色异或都等于原来颜色;白色与任何颜色异或都等于原来颜色的反色
例子:先准备一张精灵图片、一张精灵的掩膜图和一张背景图(见下图)
1.用精灵掩膜图处理精灵图片(BitBlt函数用MERGEPAINT光栅操作码):
dcFairy.BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMask,0,0,);
处理过的精灵图片:
2.用精灵掩膜图处理背景(BitBlt函数用NOTSRCERASE光栅操作码)
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcMask,0,0,NOTSRCERASE);
处理过的背景图:
3.把处理过的精灵图片贴到背景上(BitBlt函数用SRCINVERT光栅操作码)
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcFairy,0,0,);
合成后的图形:
完整代码如下:
void DrawFairy(CDC *pDC, int x, int y) // pDC为窗口DC指针
{
CDC dcFairy;
CDC dcMask;
dcFairy.CreateCompatibleDC(pDC);
dcMask.CreateCompatibleDC(pDC);
CBitmap *pMask=dcMask.SelectObject(&m_bmMask); // m_bmMask为精灵掩膜图
CBitmap *pFairy=dcFairy.SelectObject(&m_bmFairy); // m_bmFairy为精灵图片
// 得到精灵图片的大小
BITMAP bm;
m_bmFairy.GetObject(sizeof(bm),&bm);
// 处理精灵图片
dcFairy.BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMask,0,0,MERGEPAINT);
// 处理背景图片(用来贴精灵图片的那一部分)
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcMask,0,0,NOTSRCERASE);
// 将处理过的精灵图片与背景经过处理的部分"异或"起来
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcFairy,0,0,SRCINVERT);
// Release
dcMask.SelectObject(pMask);
dcFairy.SelectObject(pFairy);
}
上面是用BitBlt函数的实现方法,我们也可以用直接操作位图数据缓冲区的方法:
直接操作位图数据缓冲区:
这种方法也很简单,首先创建精灵图片和背景图片的设备无关位图对象(DIB),然后读取精灵图片各个像素的颜色值,如果颜色值等于我们设定的透明颜色(Mask Color),就把该点的颜色换为背景图上相对位置的点的颜色值,然后将经过处理的精灵图片拷贝到背景上就行了。不过需要要注意的是位图的颜色深度,不同的颜色深度决定了数据缓冲区中一个像素值的长度,需要自己写一些代码来判断这些情况,下面以一个256色的位图来做一个例子:
完整的例子代码:
void CComposeDoc::DrawFairy(HDC hDC, int left, int top, int mask)
{
LPBITMAPINFO lpbmif;
LPBITMAPINFOHEADER lpbmifh;
if ( m_hDIBFairy == NULL || m_hDIBBack == NULL ) // 分别是精灵图片的DIB对象和背景图片的DIB对象
return;
// // 得到精灵图片信息 //
lpbmifh=(LPBITMAPINFOHEADER)m_hDIBFairy;
lpbmif=(LPBITMAPINFO)m_hDIBFairy;
// 这里假设精灵图片的颜色深度为8位(256色)
ASSERT( lpbmifh->biBitCount==8 );
int cx=lpbmifh->biWidth; // 长度
int cy=lpbmifh->biHeight; // 宽度
int nBytesPerLineFairy=((lpbmifh->biWidth*lpbmifh->biBitCount+31)&~31)/8; // 每行字节数
UINT nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed :
1<<lpbmifh->biBitCount; // 颜色数
LPVOID lpvBufFairy=lpbmif->bmiColors+nColors; // 精灵图片数据指针
// // 得到背景图片信息 //
lpbmif=(LPBITMAPINFO)m_hDIBBack;
lpbmifh=(LPBITMAPINFOHEADER)m_hDIBBack;
// 同样假设背景图片的颜色深度是8位(256色)
ASSERT( lpbmifh->biBitCount == 8 );
int cxBack=lpbmifh->biWidth; // 宽度
int cyBack=lpbmifh->biHeight; // 高度
int nBytesPerLineBack=((cxBack*lpbmifh->biBitCount+31)&~31)/8;// 每行字节数
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed :
1<<lpbmifh->biBitCount;
LPVOID lpvBufBack=lpbmif->bmiColors+nColors;// 背景图片数据指针
// // 创建精灵图片的临时DIB对象 //
int nSize=GlobalSize(m_hDIBFairy);
LPVOID lpvBufTemp=GlobalAlloc(0,nSize);
if ( lpvBufTemp == NULL )
return ;
memcpy(lpvBufTemp,m_hDIBFairy,nSize);
// 由于这里假设图片的颜色深度数8位的,用BYTE指针来表示一个像素
LPBYTE lpbBufFairy=NULL;
LPBYTE lpbBufBack=NULL;
for ( int y=cy; y>0; y-- )
{
// 读取的精灵图片数据指针
lpbBufFairy=(LPBYTE)lpvBufFairy+(y-1)*nBytesPerLineFairy;
// 相对位置的背景图片数据指针
lpbBufBack=(LPBYTE)lpvBufBack+(cyBack-top-cy+y-1)*nBytesPerLineBack+left;
for ( int x=0; x<cx; x++ )
{
// 如果当前像素等于我们设定的透明颜色索引值,修改当前像素索引值
if ( *lpbBufFairy == mask )
*lpbBufFairy = * lpbBufBack;
lpbBufFairy++;
lpbBufBack++;
}
}
// 画精灵图片到屏幕上
SetDIBitsToDevice(hDC,left,top,cx,cy,0,0,0,cy,lpvBufFairy,
(LPBITMAPINFO)m_hDIBFairy,DIB_RGB_COLORS);
// 回复原来的精灵图片数据
memcpy(m_hDIBFairy,lpvBufTemp,nSize);
GlobalFree(lpvBufTemp);
}
用上面的两种方法都可以输出一个透明位图到屏幕上,只要修改在屏幕上的显示位置,就可以轻易的制作出一个精灵动画,不过用这两种方法有一个缺点,就是显示的精灵有一个明显的轮廓,特别是前景和背景颜色反差很大时。如何避免这种情况呢?就是下面要介绍的利用Alpha通道输出位图的方法。
在介绍Alpha通道之前,先来看一个如何利用Alpha值合成两张图片的效果。
Image1 Image2 合成图象
Alpha图象合成
Alpha图象合成的方法:合成图象的各点像素值是由用来制作合成图的两张图片的相应点的像素值按一定比例混合而成的,这个比例由Alpha值决定,具体算式见下:
newPixeValR= (pixel1ValR*(255-Alpha)+pixel2ValR*Alpha)/255; // Alpha取值范围从0到255
newPixeValG= (pixel1ValG*(255-Alpha)+pixel2ValG*Alpha)/255; // Alpha取值范围从0到255
newPixeValB= (pixel1ValB*(255-Alpha)+pixel2ValB*Alpha)/255; // Alpha取值范围从0到255
从上面的算式可以看出,只要修改Alpha的值,就可以改变合成后的图象中用来合成的两张图片各自所占的比值,改变合成后的显示效果。利用这个方法,我们就可以很轻易的制作出生动的淡入淡出效果和图片间的平滑过度特效。下面给出一个制作合成图的具体源码:
BOOL CompoundDIB(HANDLE hDIB,HANDLE hDIBSrc,int alpha)
{
LPVOID lpvBuf=NULL; // 目标图象数据指针
LPVOID lpvBufSrc=NULL; // 源图数据指针
// // 源图象信息 //
LPBITMAPINFO lpbmif=(LPBITMAPINFO)hDIBSrc;
LPBITMAPINFOHEADER lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
// 计算图象数据偏移量
UINT nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0; // 如果颜色数大于256色,则没有调色板
lpvBufSrc=(LPVOID)((LPBYTE)lpbmif->bmiColors+nColors*sizeof(RGBQUAD));
int cxSrc=lpbmifh->biWidth; // 源图象宽度
int cySrc=lpbmifh->biHeight; // 源图象高度
// 计算图象每行的字节数(图象位数 x 图象宽度,如果不能被2整除则在每行后面添加一个0字节)
int nBytesPerLineSrc=((cxSrc*lpbmifh->biBitCount+31)&~31)/8;
// // 目标图象信息 //
lpbmif=(LPBITMAPINFO)hDIB;
lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0;
lpvBuf=(LPVOID)((LPBYTE)lpbmif->bmiColors+nColors*sizeof(RGBQUAD));
int cx=lpbmifh->biWidth;
int cy=lpbmifh->biHeight;
int nBytesPerLine=((cx*lpbmifh->biBitCount+31)&~31)/8;
LPBYTE lpbPnt=NULL;
LPBYTE lpbPntSrc=NULL;
// // 通过alpha值合并两张图象的像素值 //
// 这里假设是24位真彩色图象,其他深度的图象处理方法可以以次类推
for ( int y=(cy<cySrc ? cy : cySrc); y>0 ;y-- )
{
lpbPnt=(LPBYTE)lpvBuf+nBytesPerLine*(y-1);
lpbPntSrc=(LPBYTE)lpvBufSrc+nBytesPerLineSrc*(y-1);
for ( int x=0; x<(cx<cxSrc ? cx : cxSrc); x++ )
{
for ( int i=0 ;i<3 ;i++ )
*lpbPnt++=(*lpbPnt*(255-alpha)+*(lpbPntSrc++)*alpha)/255;
}
}
return TRUE;
}
回到刚才讨论的问题,如何避免画出的透明图有一个明显的轮廓?想一想刚才介绍的利用Alpha值合成图象方法,如果我们在合成的过程中动态修改Alpha值,使它的轮廓部分从(背景的)0慢慢过度到(前景的)255,这样不就可以使前景逐步地渗透到背景里面了。下面来看看具体做法吧!
Alpha通道
上面所说的动态修改的Alpha值,一般是使用一张256级的灰度图来实现的(这张灰度图就称为Alpha通道),灰度图的各点值对应着前景图片相应点的Alpha值。灰度图的黑色部分是透明的(Alpha值为0),白色部分为不透明部分(Alpha值为255),灰度部分就是前景和背景的融合部分。看一看合成效果吧!
Alpha通道 前景图
背景图 合成图
可以看出,利用Alpha通道,合成后的图象前景和背景非常完美的融合在一起了。
Alpha通道合成图象代码:
BOOL CompoundDIB(int left,int top,HANDLE hDIB,HANDLE hDIBSrc,HANDLE hDIBAlpha)
{
LPVOID lpvBuf=NULL; // 目标图象数据指针(背景)
LPVOID lpvBufSrc=NULL; // 源图数据指针(前景)
LPVOID lpvBufAlpha=NULL; // Alpha通道数据指针
// // 源图象信息 //
LPBITMAPINFO lpbmif=(LPBITMAPINFO)hDIBSrc;
LPBITMAPINFOHEADER lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
// 计算图象数据偏移量
UINT nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0; // 如果颜色数大于256色,则没有调色板
lpvBufSrc=lpbmif->bmiColors+nColors;
int cxSrc=lpbmifh->biWidth; // 源图象宽度
int cySrc=lpbmifh->biHeight; // 源图象高度
// 计算图象每行的字节数(图象位数 x 图象宽度,如果不能被2整除则在每行后面添加一个0字节)
int nBytesPerLineSrc=((cxSrc*lpbmifh->biBitCount+31)&~31)/8;
// // 目标图象信息 //
lpbmif=(LPBITMAPINFO)hDIB;
lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0;
lpvBuf=lpbmif->bmiColors+nColors;
int cx=lpbmifh->biWidth;
int cy=lpbmifh->biHeight;
int nBytesPerLine=((cx*lpbmifh->biBitCount+31)&~31)/8;
// // Alpha通道信息 //
lpbmif=(LPBITMAPINFO)hDIBAlpha;
lpbmifh=(LPBITMAPINFOHEADER)hDIBAlpha;
ASSERT(lpbmifh->biWidth==cxSrc && lpbmifh->biHeight==cySrc &&
lpbmifh->biBitCount==8 );
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 256;
lpvBufAlpha=lpbmif->bmiColors+nColors;
int nBytesPerLineAlpha=((cxSrc*8+31)&~31)/8;
// // 用来读取颜色值的指针 //
LPBYTE lpbPnt=NULL;
LPBYTE lpbPntSrc=NULL;
LPBYTE lpbPntAlpha=NULL;
// // 通过alpha值合并两张图象的像素值 //
// 这里假设是24位真彩色图象,其他深度的图象处理方法可以以次类推
for ( int y=cySrc; y>0 ;y-- )
{
lpbPnt=(LPBYTE)lpvBuf+nBytesPerLine*(cy-top-cySrc+y-1)+left*3;
lpbPntSrc=(LPBYTE)lpvBufSrc+nBytesPerLineSrc*(y-1);
lpbPntAlpha=(LPBYTE)lpvBufAlpha+nBytesPerLineAlpha*(y-1);
for ( int x=0; x<cxSrc; x++ )
{
int alpha=*lpbPntAlpha++;
for ( int i=0 ;i<3 ;i++ )
*lpbPnt++=(*lpbPnt*(255-alpha)+*(lpbPntSrc++)*alpha)/255;
}
}
return TRUE;
}
上面介绍的只是几种简单的图象合成知识。直接修改DIB位图数据制作合成图象,基本方法就是按照一定的算法来从新组合位图的光栅数据,形成不同的图象效果。写这篇文章,也就是希望能给大家起到一个抛砖引玉的作用,给大家一个提示,帮助大家做出更好的图形特效。