如何使HBITMAP显示成透明效果
将得到的HBITMAP显示到界面上,方法很多,最常用的就是用GDI+的Graphics对象,把它画到界面上:
Graphics gg(hdc); Bitmap *p = Bitmap::FromHBITMAP(g_hBitmap, NULL); gg.DrawImage(p, 0, 132, p->GetWidth(), p->GetHeight());
但这会有一个问题,Bitmap::FromHBITMAP好像有一个BUG,就是会把HBITMAP里面所带有的Alpha信息丢掉,所以出来的图就像下图:
大家都知道,HBITMAP是有Alpha信息的,有时候我们希望将HBITMAP显示成透明,如何才能做到呢?有两种方法:
一、生成一张掩码,将掩码与原始图进行或操作,再绘制到DC上面。
二、利用GDI+的Bitmap*类。
这两种方法都是可以的,第一种情况大多数可以,但有时就不行,比如,如果HBITMAP有阴影的话,它生成的掩码就会比原始图大一些,进行或操作手,看到的图片周围就会有黑边。所以,第一种方法不是最终解决方案。
效果图如下:
图二 经过掩码处理的效果
第二种呢?
先看效果吧!
图三 用第二种方法的效果
这个效果好吧,是怎么做到的呢?请往下看。
在这里,我假设你已经得到了HBITMAP,它里面是一个Excel图标(至于如何得到,那是另外一回事)。
这里,肯定是不能用GDI+的Bitmap::FromHBITMAP了,因为你不知道它里面做过什么操作。
首先,要根据HBITMAP得到它的信息,宽度、高度、位数等信息,用GetObject函数得到。
BITMAP结构里面包含有指向位图数据的指针(但有时候,得到的是NULL)。
第二,得到信息后,就要创建一个Bitmap*对象了,它的大小与HBITMAP相同。
第三,通过Bitmap::LockBits函数得到BitampData数据,这个结果里面包含了指向Bitmap对象位图数据的指针,所以我们要得到这个指针,把HBITMAP里面的数据,依次拷贝到这个地址中。
第四,调用Bitmap::UnlockBits函数把缓冲区的数据写到Bitmap中。到这里,基本上就结束了。
上面的三步是这整个操作的流程,但中间会涉及到很多问题
1)BITMAP结构里面的数据指针为NULL怎么办。
2)BITMAP结构里的数据是从上到下(top-down)还是从下到上(botton-up),因为这关系到复制数据时的拷贝方式,从前到后面,还是从后面到前面。不知道,就有可能出现下面这种情况(图像翻转了180度)
图四 不清楚数据排列顺序可能导致的效果
关于第一个问题,应该仔细看MSDN,GetObject函数的Remark部分说得很清楚,什么指针为NULL,什么时候不是。但是,就算你得到了指针,你也不知道其内部是怎么排列,不知道就可能造成图形翻转的情况,所以HBITMAP的数据应该始终自己去取。
如何去取一个HBITMAP所对应的数据呢?
MSDN上面说得很明显,If hgdiobj is a handle to a bitmap created by any other means, GetObject returns only the width, height, and color format information of the bitmap. You can obtain the bitmap's bit values by calling the GetDIBits or GetBitmapBits function.
GetBitmapBits,是针对16位操作系统的。所以一般不用,因为现在几乎没有人在和16位的系统。就算有,也是少数情况。那就只能用GetDIBits函数了,用之前请务必把MSDN看清楚,不然很容易出错。(具体实现见后面的实现代码)
关于第二个问题,也请看MSDN,在GetDIBits函数的Remark部分,A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. The bitmap color table will be appended to the BITMAPINFO structure. 这段话的意思就是说,BITMAPINFO结构里面的BITMAPINFOHEADER结构里面的成员biHeight如果是正数,那么得到的数据就是从下到上,如果是负数,数据就是从上到下。所以就给这个成员赋值时,请一定要注意了。
如果你得到的是一个HICON,你也想达到这种效果的话,那么你应该首先把这个HICON的HBITMAP取出来,通过GetIconInof这个函数,传一个ICONINFO结构休,它会把ICON的信息填充到这个结构体中。
好了,问题点都差不多了,直接贴代码。
这个函数很独立,直接拷过去就能用的。
Bitmap* CreateBitmapFromHBITMAP(IN HBITMAP hBitmap) { BITMAP bmp = { 0 }; if ( 0 == GetObject(hBitmap, sizeof(BITMAP), (LPVOID)&bmp) ) { return FALSE; } // Although we can get bitmap data address by bmp.bmBits member of BITMAP // which is got by GetObject function sometime, // we can determine the bitmap data in the HBITMAP is arranged bottom-up // or top-down, so we should always use GetDIBits to get bitmap data. BYTE *piexlsSrc = NULL; LONG cbSize = bmp.bmWidthBytes * bmp.bmHeight; piexlsSrc = new BYTE[cbSize]; BITMAPINFO bmpInfo = { 0 }; // We should initialize the first six members of BITMAPINFOHEADER structure. // A bottom-up DIB is specified by setting the height to a positive number, // while a top-down DIB is specified by setting the height to a negative number. bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.bmiHeader.biWidth = bmp.bmWidth; bmpInfo.bmiHeader.biHeight = bmp.bmHeight; // 正数,说明数据从下到上,如未负数,则从上到下 bmpInfo.bmiHeader.biPlanes = bmp.bmPlanes; bmpInfo.bmiHeader.biBitCount = bmp.bmBitsPixel; bmpInfo.bmiHeader.biCompression = BI_RGB; HDC hdcScreen = CreateDC(L"DISPLAY", NULL, NULL,NULL); LONG cbCopied = GetDIBits(hdcScreen, hBitmap, 0, bmp.bmHeight, piexlsSrc, &bmpInfo, DIB_RGB_COLORS); DeleteDC(hdcScreen); if ( 0 == cbCopied ) { delete [] piexlsSrc; return FALSE; } // Create an GDI+ Bitmap has the same dimensions with hbitmap Bitmap *pBitmap = new Bitmap(bmp.bmWidth, bmp.bmHeight, PixelFormat32bppPARGB); // Access to the Gdiplus::Bitmap's pixel data BitmapData bitmapData; Rect rect(0, 0, bmp.bmWidth, bmp.bmHeight); if ( Ok != pBitmap->LockBits(&rect, ImageLockModeRead, PixelFormat32bppPARGB, &bitmapData) ) { SAFE_DELETE(pBitmap); return NULL; } BYTE *pixelsDest = (BYTE*)bitmapData.Scan0; int nLinesize = bmp.bmWidth * sizeof(UINT); int nHeight = bmp.bmHeight; // Copy pixel data from HBITMAP by bottom-up. for ( int y = 0; y < nHeight; y++ ) { // 从下到上复制数据,因为前面设置高度时是正数。 memcpy_s( (pixelsDest + y * nLinesize), nLinesize, (piexlsSrc + (nHeight - y - 1) * nLinesize), nLinesize); } // Copy the data in temporary buffer to pBitmap if ( Ok != pBitmap->UnlockBits(&bitmapData) ) { delete pBitmap; } delete [] piexlsSrc; return pBitmap; }
得到Bitmap对象后,就可以它这个对象画在界面上了
Graphic g(hdc);
g.DrawImage(pBitmap, 0, 0, pBitmap->GetWidth(), pBitmap->GetHeight());