摘自<<http://www.bc99.cn/Article/ASPX/2007-3-27-cs/65D5KI8EJFGK.html>> 绘制半透明位图 有的时侯,我们希望显示一幅半透明的位图。也就是说我们将一幅位图B 显示到A位图上,又希望透过B位图看到A位图的一部分图像但不是全部。比如A位 图是一幅曲线图,B是一幅提示位图,我们想在显示提示的同时看到已显示的曲 线,但不需要曲线的背景,就需有用到半透明位图。曲线看上去就象从B位图中渗 透过来,其实半透明技术就是一种渗透技术,渗透公式我们可选用多种,在这里 我们选用(A AND 0x7F)OR B。注意,白色不能产生渗透。 //参数说明: //hDIB -位图句柄 //pPal -位图调色板 //xDest -显示位图的左上角x坐标 //yDest -显示位图的左上角y坐标 void DrawSemiTransparentBitmap(CDC *pDC, int nXDest, int nYDest, HGLOBAL hDIB,CPalette *pPal) BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ; int nColors = bmInfo.bmiHeader.biClrUsed ? bmInfo.bmiHeader.biClrUsed : 1 << bmInfo.bmiHeader.biBitCount; int nWidth = bmInfo.bmiHeader.biWidth; int nHeight = bmInfo.bmiHeader.biHeight; LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors); CDC memDC; memDC.CreateCompatibleDC( pDC ); CBitmap bmp; bmp.CreateCompatibleBitmap( pDC, nWidth, nHeight ); CBitmap *pOldBitmap = memDC.SelectObject( &bmp ); if( pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE&&nColors<256) CPalette *pOldMemPalette = memDC.SelectPalette(pPal, FALSE); memDC.RealizePalette(); ::SetDIBitsToDevice(memDC.m_hDC, 0, 0, nWidth, nHeight, 0, 0, 0, nHeight, lpDIBBits, (LPBITMAPINFO)hDIB, DIB_RGB_COLORS); CDC maskDC; CBitmap mbm; maskDC.CreateCompatibleDC(pDC); mbm.CreateCompatibleBitmap(pDC, nWidth, nHeight); maskDC.SelectObject(&mbm); maskDC.FillSolidRect(CRect(0, 0, nWidth, nHeight), RGB(0x7F, 0x7F, 0x7F)); pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &maskDC, 0, 0, SRCAND); pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &memDC, 0, 0, SRCPAINT); memDC.SelectObject(pOldBitmap); } 如何画透明位图 在丰富多彩的软件世界中,位图的处理技术尤为重要。透明位图的显示作为 一种常用的图像处理方法,被用在众多的软件中。其基本原理,也就是将一幅需 要透明显示的位图(其透明色为已知),制作出二幅需要的位图A与B,其中A为除 透明色外均填充为黑色,B为把透明色填充为黑色其余色不变,再用指定光栅操作 将两幅位图合并,可形成透明位图。 创建过程如下: 1、使用透明色背景,将位图拷贝到内存屏蔽位图中。 2、利用与白色相与不变色,与黑色相与成黑色的原理,将内存位图的的背景设置 成黑色、前景设置成白色,并将屏蔽位图用与操作拷贝到此内存位图中。形成位图B。 3、将显示设备背景设置为白色、前景设置成黑色,并将屏蔽位图用与操作拷贝到 显示设备中。形成位图A。 4、将内存位图用或操作拷贝到显示设备中。最终形成透明位图。 //参数说明: //hDIB -位图句柄 //pPal -位图调色板 //xDest -显示位图的左上角x坐标 //yDest -显示位图的左上角y坐标 //colorTransparent -透明色 void DrawTransparentBitmap( CDC *pDC, int nXDest, int nYDest,HGLOBAL hDIB, COLORREF colorTransparent, CPalette *pPal) { BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ; int nColors = bmInfo.bmiHeader.biClrUsed ? bmInfo.bmiHeader.biClrUsed :1<
int nWidth = bmInfo.bmiHeader.biWidth; int nHeight = bmInfo.bmiHeader.biHeight; LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors); CDC memDC,maskDC; memDC.CreateCompatibleDC( pDC ); CBitmap bmp; bmp.CreateCompatibleBitmap( pDC, nWidth, nHeight ); CBitmap *pOldBitmap = memDC.SelectObject( &bmp ); if( pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE&&nColors<256) CPalette *pOldMemPalette = memDC.SelectPalette(pPal, FALSE); memDC.RealizePalette(); ::SetDIBitsToDevice(memDC.m_hDC, 0, 0, nWidth, nHeight, 0, 0, 0, nHeight, lpDIBBits, (LPBITMAPINFO)hDIB, DIB_RGB_COLORS); maskDC.CreateCompatibleDC(pDC); CBitmap maskBitmap; maskBitmap.CreateBitmap( nWidth, nHeight, 1, 1, NULL ); maskDC.SelectObject( &maskBitmap ); memDC.SetBkColor( colorTransparent ); maskDC.BitBlt( 0, 0, nWidth, nHeight, &memDC, 0, 0, SRCCOPY ); memDC.SetBkColor(RGB(0,0,0)); memDC.SetTextColor(RGB(255,255,255)); memDC.BitBlt(0, 0, nWidth, nHeight, &maskDC, 0, 0, SRCAND); pDC->SetBkColor(RGB(255,255,255)); pDC->SetTextColor(RGB(0,0,0)); pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &maskDC, 0, 0, SRCAND); pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &memDC,0, 0, SRCPAINT); memDC.SelectObject( pOldBitmap ); } 画透明位图通常的方法是使用遮罩。所谓遮罩就是一张黑白双色的位图,他和 要透明的位图是对应的,遮罩描述了位图中需要透明的部分,透明的部分是黑色的, 而不透明的是白色的,白色的部分就是透明的部分。 假设图A是要画的透明位图,图B是遮罩,图A上是一个大写字母A,字母是红色的,背 景是黑色的,图B背景是白色的,上面有一个黑色的字母A和图A的形状是一样的。 比如我们要在一张蓝天白云的背景上透明地画图A,就是只把红色的字母A画上去。我 们可以先将图B和背景进行与操作,再把图B和背景进行或操作就可以了。 用VC++ MFC实现的代码如下: void CDemoDlg::OnPaint() CPaintDC dc(this); Cbitmap BmpBack,BmpA,BmpB,*pOldBack,*pOldA,*pOldB; BmpBack.LoadBitmap(IDB_BACKGROUND); // 载入背景图 BmpA.LoadBitmap(IDB_BITMAPA); //载入图A BmpB.LoadBitmap(IDB_BITMAPB); //载入图B CDC dcBack,dcA,dcB; //声明三个内存DC用于画图 dcBack.CreateCompatibleDC(&dc); dcA.CreateCompatibleDC(&dc); dcB.CreateCompatibleDC(&dc); //把这三个内存DC创建成和PaintDC兼容的DC pOldBack=dcBack.SelectObject(&BmpBack); pOldA=dcA.SelectObject(&BmpA); pOldB=dcB.SelectObject(&BmpB); //把三个位图选入相应的DC dc.BitBlt(0,0,100,100,&dcBack,0,0,SRCCOPY); //画背景 dc.BitBlt(0,0,48,48,&dcB,0,0,SRCAND); //用与的方式画遮罩图B dc.BitBlt(0,0,48,48,&dcA,0,0,SRCPAINT); //用或的方式画遮图A dcBack.SelectObject(pOldBack); dcBack.SelectObject(pOldA); dcBack.SelectObject(pOldB); //从内存DC中删除位图 你会看到红色的字母A透明地画在背景上了。 用遮罩的方法必须事先做好遮罩,遮罩和位图大小一样等于多消耗一倍的资源, 比较浪费。还有一种画透明位图的方法,基本原理是一样的,只是不用事先做好 遮罩,根据需要动态生成遮罩,但是要求需要透明的位图必须指定一种透明色, 凡是这个透明色的地方则画成透明的。 用VC++ MFC实现的代码如下: /* 这是一个用来画透明位图的函数 CDC *pDC 需要画位图的CDC指针 UINT IDImage 位图资源ID Crect &rect 指定位图在pDC中的位置 COLORREF rgbMask 位图的透明色 */ void DrawTransparentBitmap(CDC *pDC, UINT IDImage,Crect &rect, COLORREF rgbMask) { CDC ImageDC,MaskDC; Cbitmap Image,*pOldImage; Cbitmap maskBitmap,*pOldMaskDCBitmap ; Image.LoadBitmap(IDImage); ImageDC.CreateCompatibleDC(pDC); pOldImage=ImageDC.SelectObject(&Image); MaskDC.CreateCompatibleDC(pDC); maskBitmap.CreateBitmap( rect.Width(), rect.Height(), 1, 1, NULL ); pOldMaskDCBitmap = MaskDC.SelectObject( &maskBitmap ); ImageDC.SetBkColor(rgbMask); MaskDC.BitBlt( 0, 0, rect.Width(), rect.Height(), &ImageDC, 0, 0, SRCCOPY ); ImageDC.SetBkColor(RGB(0,0,0)); ImageDC.SetTextColor(RGB(255,255,255)); ImageDC.BitBlt(0, 0, rect.Width(), rect.Height(), &MaskDC, 0, 0, SRCAND); pDC->BitBlt(rect.left,rect.top,rect.Width(), rect.Height(), &MaskDC, 0, 0, SRCAND); pDC->BitBlt(rect.left,rect.top,rect.Width(), rect.Height(), &ImageDC, 0, 0,SRCPAINT); MaskDC.SelectObject(pOldMaskDCBitmap); ImageDC.SelectObject(pOldImage); } void CDemoDlg::OnPaint() { CPaintDC dc(this); Cbitmap BmpBack,*pOldBack,; BmpBack.LoadBitmap(IDB_BACKGROUND); CDC dcBack; dcBack.CreateCompatibleDC(&dc); pOldBack=dcBack.SelectObject(&BmpBack); dc.BitBlt(0,0,100,100,&dcBack,0,0,SRCCOPY); DrawTransparentBitmap(&dc,IDB_BITMAPA,Crect(0,0,48,48),RGB(192,192,0)); dcBack.SelectObject(pOldBack); } //XOR 方式 http://hi.baidu.com/goldenypb/blog/item/39fa8cddf769f0355882dd6c.html 首先是异或运算: 异或的运算方法是一个二进制运算: 异或的运算方法是一个二进制运算: 1^1=0 0^0=0 1^0=1 0^1=1 两者相等为0,不等为1. 这样我们发现交换两个整数的值时可以不用第三个参数。 如a=11,b=9.以下是二进制 a=a^b=1011^1001=0010; b=b^a=1001^0010=1011; a=a^b=0010^1011=1001; 这样一来a=9,b=11了。 再就是sgyxslsc先生的这篇文章(原文出处:http://sgyxslsc.spaces.live.com/blog/)
异 或运算有这么个特点:用一个数B与另一个数A进行两次异或运算,那么A的值保持不变。可以设想一下,在显示位图的时候,图片中的位与欲显示的位图的区域进 行两次异或操作时,欲显示的位图的区域不会产生任何变化。也就是说,如果将图片中的背景色与欲显示的位图的区域进行两次异或运算,那么背景色就被过滤掉 了,但同样,位图中非背景的内容也被过滤掉了。
现在我们这样,第一次异或运算之后,加这样一个操作。把位图中的主要内容与0作与运算,而背景区域与1作与运算。这样位图的主要内容区变为都变为0,而背景区域还是原图像与窗口背景的异或值。这样我们再进行第二次异或操作,位图的主区域与0异或保持不变,而背景区域被消除了。
而执行这个操作需要我们建立一个“面具”图片,这个图片只有两种颜色:黑和白。黑色的RGB值全部是0组成,白色的RGB值肯定是全部由1组成。而这张图 片的特点就是 主要内容的值都是0(黑),背景色都是1(白)。在两次异或的中间用这个图片以与的方式在显示区域显示就可以了。
在BitBlet函数中,“异或”对应于“SRCINVERT”,“与”对应于“SRCCOPY”。下面是书中的程序:
void CTransBitmapDlg::OnPaint()
{
CDC *PDC = GetDC();
CDC dcImage, dcTrans;
//装入欲显示的位图
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAPID);
//取得位图的详细信息
BITMAP bm;
bitmap.GetBitmap(&bm);
int nWidth = bm.bmWidth;
int nHeight = bm.bmHeight;
//建立与设备环境相关的位图,dcImage中用来装入欲显示的位图
//dcTrans中用来装入过滤背景色的位图
dcImage.CreateCompatibleDC(pDC);
dcTrans.CreateCompatibleDC(pDC);
CBitmap *pOldBitmapImage = dcImage.SelectObject(&bitmap);
// 建立与欲显示的位图等大的单色位图,用来过滤背景色
CBitmap bitmapTrans;
// “面具”就在这步做好
bitmapTrans.CreateBitmap(nWidth, nHeight, 1, 1, NULL);
CBitmap *pOldBitmapTrans = dcTrans.SelectObject(&bitmapTrans);
//强行将欲显示的位图的背景色设置为白色,以方便建立dcTrans, 这步很重要。如果你的
//位图的背景色不是白色的,你只需要修改这一步的参数。
dcImage.SetBkColor(RGB(255, 255, 255));
dcTrans.BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCCOPY);
//在dcTrans中,背景色变为白色,而主要图像的各种色彩都被设置成黑色
//显示“面具”,这一步主要目的的主要目的在于让你更能够明白“面具”的真面目
pDC->BitBlt(0, bm.bmHeight, nWidth, nHeight, &dcTrans, 0, 0, SRCCOPY);
pDC->BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);
//该步骤将源位图与目标区域进行异或运算
pDC->BitBlt(0, 0, nWidth, nHeight, &dcTrans, 0, 0, SRCAND);
//通过dcTrans与目标区域进行“与”操作,建立了与主要图像轮廓一致的黑框以保护图片的主要内容不会改变
pDC->BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);
//用黑框位图与源位图进行黑色异或运算,将源码位图中有色彩(除背景色以外)
//的内容还原。在这步,背景色经过与目标区域进行了两次异或运算,背景色已经被过滤
}
} 其中
CreateBitmap不明白,怎么利用它就创建出白底黑面的位图了,又看了疯狐先生的文章,一切都明白了:
函数功能:该函数创建一个带有特定宽度、高度和颜色格式的位图。
函数原型:HBITMAP CreateBitmap(int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel, CONST VOID *lpvBits);
参数:
nWidth:指定位图宽度,单位为像素。
nHeight:指定位图高度,单位为像素。
cPlanes:指定该设备使用的颜色位面数目。
cBitsPerPel:指定用来区分单个像素点颜色的位数(比特数目)。
lpvBits:指向颜色数据数组指针。这些颜色数据用来置矩形区域内像素的颜色。矩形区域中的每一扫描线必须是双字节整数倍(不足部分以0填充)。如果该参数为NULL,那么就表示没有定义新位图。
备注:在创建完位图后,可以通过使用SelectObject函数把它选入到设备环境中。尽管函数CreateBitmap可以用来创建彩色位图, 但由于性能方面的原因,应用程序使用CreateBitmap函数来创建单色位图,创建彩色位图应该使用函数 CreateCompatibleBitmap。当由CreateBitmap创建而返回的彩色位图被选入到设备环境时,系统必须确保选入进去的设备环境 格式与位图匹配。由于函数CreateComapatibleBitmap获取设备环境,所以它返回的位图与指定的设备环境有相同的格式。所以对Select的后续调用都要比从CreateBitmap函数创建返回的彩色位图调用快。
如果位图是单色的,那么对于目标设备环境而言,0代表前景颜色,而1表示背景颜色。
如果应用程序将nWidth或nHeight参数设为0,那么函数CreateBitmap返回的是只有一个像素的单色位图句柄。当不再需要位图时,可调用DeleteObject函数删除它。
Windows CE:参数cPlanes必须是1。
|