C++ 代码

多种手机平台开发-----让我们跟苹果一起红起来。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

透明位图原理及代码

Posted on 2009-02-16 20:40  蔡清华  阅读(6467)  评论(0编辑  收藏  举报
(半) 透明位图原理及代码

摘自<<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。