之所以将GDI+ Bitmap和GDI HBITMAP互转单独挑出来写下,是因为实际应用中经常我们需要GDI和GDI+换用,特别是需要兼顾效率和渲染效果的场合,更是通常使用GDI+做平滑/抗锯齿,用GDI做常规绘图。

熟悉GDI+的人已经知道,GDI+本身已经提供了HBITMAP和Bitmap转换的函数:

HBITMAP->Bitmap 构造函数或FromHBITMAP

Bitmap->HBITMAP GetHBITMAP

对于无透明度的图像来说,直接调用没有问题,但是对应带透明度的图像,使用时就有问题了,所以本文重点讨论的是带透明度的HBITMAP和Bitmap的转换

1.Bitmap->HBITMAP

msdn函数原型如下:

Status GetHBITMAP([in, ref]  const Color &colorBackground,[out]      HBITMAP *hbmReturn
);

重要的是这里的colorBackground参数,这个参数微软没有给出详细说明,很容易误用。注意两点:

1.Color中的Alpha参数无用,这里纯粹是因为Color包含Alpha才不得不指明这个参数

2.这个参数主要是针对带Alpha通道的图,可以将透明背景替换成指定的单色背景,对于不透明图无法指明其背景色

对于如下带透明背景的图像

编写如下代码,注意这里输入Color使用的是默认值(0):

 void DrawImages3(HDC &hdc){RECT rect = {0,0,500,500};FillRect(hdc, &rect, (HBRUSH)::GetStockObject(GRAY_BRUSH));Bitmap bmp1(L".\\pop_bk.png");//CGdiplusConvertHelper::DumpBitmap(bmp1);HBITMAP hBitmap= NULL;Color clr(0,0,0,0); if (Gdiplus::Ok == bmp1.GetHBITMAP(clr, &hBitmap))//转换完成的是预乘Alpha的图像{MyTrace(L"Bkclr %d-%d-%d-%d PixelFormat:%d %d %d %d", clr.GetRed(), clr.GetGreen(), clr.GetBlue(), clr.GetAlpha(), bmp1.GetPixelFormat(), PixelFormat24bppRGB, PixelFormat32bppARGB, PixelFormat32bppPARGB);//CGdiplusConvertHelper::DumpHBITMAP(hdc, hBitmap);HDC hMemDC = ::CreateCompatibleDC(hdc);HBITMAP hOld = (HBITMAP)SelectObject(hMemDC, hBitmap);BLENDFUNCTION blendfunc = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};AlphaBlend(hdc, 0, 0, bmp1.GetWidth(), bmp1.GetHeight(),hMemDC,0, 0, bmp1.GetWidth(), bmp1.GetHeight(),blendfunc);SelectObject(hMemDC, hOld);DeleteDC(hMemDC);DeleteObject(hBitmap);}}

这里为了突出透明,把背景画成灰色,转换完成的HBITMAP,使用AlphaBlend绘制,效果如下,背景透明,没有问题。

和注释标注的一样,注意这里转换完成的就是Alpha通道预乘过的图,AlphaBlend要求必须是预乘过的图,所以刚好没有问题。为了验证确实生成的图确实是预乘过的图,可以打开代码中的两行注释,debugview分别输出Bitmap和HBITMAP中的像素值,选择其中的部分转换前后对应的像素如下,

比如87-141-202-204 -> 70-113-162-204

70=87*204/255

113=141*204/255

162=202*204/255

所以转换的图是Alpha预乘过的图

这里输出Bitmap每行像素很简单,调用GetPixel即可,遍历HBITMAP的代码稍后再讲。

下面我们把输入的Color=(0,255,0,0),即把透明背景换成红色 显示,结果如下

这个红色怎么看起来这么怪呢,感觉比真正的红色要浅一些,什么原因呢?接着我们再用上面的方法,看下转换前后的对应像素值:

原因还是我们刚才说的,转换时Alpha不会被调整,所以转换完成后的图像的原先透明背景处虽然RGB=(255,0,0),但是Alpha=0表示还是透明的,使用AlphaBlend显示时就会和背景混合造成颜色变浅

解决这个问题很简单,既然我们指明了背景色,就是想把透明背景换掉,这时候使用AlphaBlend显示时,第四个参数不应该是AC_SRC_ALPHA而是0,这样表明要显示的图不使用透明度,这样就解决问题了。调整参数后,结果如下,完美。

2.HBITMAP->Bitmap

如下,使用上文中的方法生成带透明度的HBITMAP,再使用Bitmap构造函数转换HBITMAP(FromHBITMAP效果一样)为Bitmap,最后显示出来

 void DrawImages4(HDC &hdc){RECT rect = {0,0,500,500};FillRect(hdc, &rect, (HBRUSH)::GetStockObject(GRAY_BRUSH));Bitmap bmp1(L".\\pop_bk.png");HBITMAP hBitmap= NULL;Color clr; if (Gdiplus::Ok == bmp1.GetHBITMAP(clr, &hBitmap))//转换完成的是预乘Alpha的图像{CGdiplusConvertHelper::DumpHBITMAP(hdc, hBitmap);//透明HBITMAP转BitmapBitmap bmp2(hBitmap, NULL);CGdiplusConvertHelper::DumpBitmap(bmp2);//绘制BitmapGraphics g(hdc);g.DrawImage(&bmp2, 0, 0);DeleteObject(hBitmap);}}

显示效果如下:

透明背景全部变成黑色了,这是为什么,还是使用前文方法,查看对应像素值如下

可以看到,之前显示透明的像素,RGB值没有变,但是透明度变成255=完全不透明,而RGB(0,0,0)刚好是黑色,其他的Bitmap的透明度也都为255,这说明转换过程中透明度丢失了,因此对于带透明度的HBITMAP,这种方法是行不通的。这里多说一句,为什么微软这里实现不把透明度带上呢,结合GDI Bitblt等函数也都不支持Alpha操作,我猜可能是GDI+设计时候HBITMAP支持Alpha还比较弱,所以干脆不读取了,可能还是有别的考虑,知道内幕的请留言,学习下。

下面接着说,怎么才能够完成带透明度的HBITMAP转Bitmap的操作呢,我们知道两者的格式不一样,但是他们的数据都是一样的,所以考虑自己创建一个带透明度的Bitmap,从HBITMAP中读取数据,填充到Bitmap对应位置即可,具体操作如下:

1.读取HBITMAP信息

 //获取DDB或DIB信息,DDB和hdc相关,DIB和实际数据相关  static BOOL GetDIBitsInfo(const HDC &hdc, const HBITMAP &hBitmap, BITMAPINFOHEADER &bmih){bmih.biSize = sizeof(BITMAPINFOHEADER);  //暂时未知颜色表大小,先只填充BITMAPINFOHEADER,BI_BITFIELDS需要多余数据也不填充bmih.biBitCount = 0;                    //不填充颜色表,此时查询图像属性  if (0 == GetDIBits(hdc,   hBitmap,   0, 0,   NULL, (BITMAPINFO *)&bmih,   DIB_RGB_COLORS))  {  return FALSE;  }  return TRUE;}

2.读取HBITMAP数据

 //按照指定格式导出数据,不必和上述获取的保持一致,GetDIBits会自动转换static PBYTE GetDIBitsData(const HDC &hdc, const HBITMAP &hBitmap, BITMAPINFOHEADER &bi){bi.biSize = sizeof(BITMAPINFOHEADER);    //bi.biWidth = bi.biWidth;   bi.biHeight = abs(bi.biHeight);//数据自底向上bi.biPlanes = 1;       bi.biBitCount = (bi.biBitCount==32) ? 32 : 24;//不带透明度的统一输出为24bit图bi.biCompression = BI_RGB;    bi.biSizeImage = 0;  bi.biXPelsPerMeter = 0;    bi.biYPelsPerMeter = 0;    bi.biClrUsed = 0;    bi.biClrImportant = 0;//分配数据存储区   int nLineSize = (((bi.biBitCount*bi.biPlanes*bi.biWidth + 31) & ~31) >> 3);//DIB每行为4字节倍数,向上取整,不足的每行后面填充0DWORD dwBitsSize = bi.biHeight * nLineSize;BOOL bRet = FALSE;PBYTE pBits = NULL;do {pBits = (PBYTE)malloc(dwBitsSize);  if (NULL == pBits)  {    break;}  //读入位图信息和位图数据if (0 == GetDIBits( hdc,   hBitmap,   0, bi.biHeight,   pBits, (BITMAPINFO *)&bi,   DIB_RGB_COLORS))  {   break;}bRet = TRUE;} while (false);if (!bRet){FreeDIBitsData(pBits);}return bRet ? pBits : NULL;}

3.根据HBITMAP创建对应Bitmap,并将HBITMAP数据拷贝到Bitmap对应数据区

static Bitmap* HBITMAPToBitmap(HDC hdc, HBITMAP hBitmap){if (!hdc || !hBitmap){return NULL;}BITMAPINFOHEADER bmih={0};if (!GetDIBitsInfo(hdc, hBitmap, bmih)){return NULL;}PBYTE pBits = NULL;Bitmap* pBitmap = NULL;BOOL bRet = FALSE;do {if (NULL == (pBits=GetDIBitsData(hdc, hBitmap, bmih))){break;}//创建Bitmap信息PixelFormat pf   = bmih.biBitCount==32 ? PixelFormat32bppPARGB : PixelFormat24bppRGB;LONG cyHeight    = abs(bmih.biHeight);UINT nLineSize    = (((bmih.biBitCount*bmih.biPlanes*bmih.biWidth + 31) & ~31) >> 3);pBitmap = new Bitmap(bmih.biWidth, cyHeight, pf);if (!pBitmap){break;}//填充GDI+ Bitmap数据BitmapData bitmapData;Rect rect(0, 0, bmih.biWidth, cyHeight);if ( Gdiplus::Ok != pBitmap->LockBits(&rect, ImageLockModeRead, pf, &bitmapData) ){break;}BYTE *pDestBits = (BYTE*)bitmapData.Scan0;for ( int y = 0; y < cyHeight; y++ )// 从下到上复制数据,因为前面设置高度时是正数。{memcpy_s( (pDestBits + y * nLineSize), nLineSize, (pBits + (cyHeight - y - 1) * nLineSize), nLineSize);}if ( Gdiplus::Ok != pBitmap->UnlockBits(&bitmapData)){break;}bRet = TRUE;} while (false);FreeDIBitsData(pBits);if (!bRet){delete pBitmap;pBitmap = NULL;}return pBitmap;}

所以,HBITMAP转换和显示代码,如下:

 void DrawImages5(HDC &hdc){RECT rect = {0,0,500,500};FillRect(hdc, &rect, (HBRUSH)::GetStockObject(GRAY_BRUSH));Bitmap bmp1(L".\\pop_bk.png");HBITMAP hBitmap= NULL;Color clr; if (Gdiplus::Ok == bmp1.GetHBITMAP(clr, &hBitmap))//转换完成的是预乘Alpha的图像{//转换和显示Bitmap *pbmp2 = CGdiplusConvertHelper::HBITMAPToBitmap(hdc, hBitmap);if (pbmp2){Graphics g(hdc);g.DrawImage(pbmp2, 0, 0);CGdiplusConvertHelper::DumpBitmap(*pbmp2);delete pbmp2;}DeleteObject(hBitmap);}}

显示如下,完美。到此转换代码讲解完,这时候回归之前的一个问题,如何输出HBITMAP每行像素,其实就是和这里的处理方式一样,不同的只是把 拷贝过程换成了输出过程。

本文查看每行像素和转换代码已经封装成一个帮助类CGdiplusConvertHelper,下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

3. GDI+ Bitmap和GDI HBITMAP互转相关推荐

  1. GDI+ Bitmap与WPF BitmapImage的相互转换

    原文:GDI+ Bitmap与WPF BitmapImage的相互转换 using System.Windows.Interop; //... // Convert BitmapImage to Bi ...

  2. C# 中的 gdi 而不是 gdi+

    我记得有多种方法的,不过我现在的部分资料还没恢复,下面这个是网友的. 我记得我用是因为 gdi+ 的字符串绘画时无法限制在区域内,而 gdi 是可以的.这实在是奇怪. ---------------- ...

  3. 提取 Office 2016 工具栏图标

    Office 图标精美漂亮,作为微软的官方图标,与 Windows 具有一致的风格,但我们若想把这些图标用在自己的程序中,却并不容易,使用常规的提取程序资源的方法,根本得不到这些图标. 微软虽然没有把 ...

  4. VC++界面编程之--实现一个画板并提供文字输入功能(MsPaint)

    画板的实现非常有趣,首先要新建一张HBITMAP来保存原来的绘制内容,然后需要创建一个可拉伸的输入框,供用户输入文字.必要时还得提供UnDo功能,供用户取消上一次绘制的功能. 利用业余时间,我制作了一 ...

  5. C#模拟PrtScn实现截屏

    有了之前的基础知识了解,现在开始实现PrtScn和Alt+PrtScn. 首先新建一个WPF应用程序,命名为PrintscreenAndAltPrintScreen 导入keybd_event方法: ...

  6. 关于HBITMAP,CBITMAP,BITMAP的转换以及图像显示的一点归纳

    OK,在上一篇文章中我提到了VC6和VS的差别,在VC6中我们只能依赖于CBITMAP HBITMAP以及BITMAP之间的转化关系,而在VS中,我们用CIMAGE类就可以全部搞定,那么究竟这三个类之 ...

  7. 学习Qt使用GDI+绘图笔记

    GDI+学习笔记 1.GDI+介绍: 2.GDI+的载入与卸载: 3.Qt中GDI+的使用 (1).pro文件设置 (2)widget属性设置 (3)重载painEngine()方法 (4)获取HDC ...

  8. Win32 GDI 学习总结

    Windows GDI 教程(一) 一个简单的绘图程序 http://www.tuicool.com/articles/jeMBZ3v 常见的图形编程库,除了 GDI 外还有 GDI+.OpenGL. ...

  9. Qt使用GDI绘图(仅Windows平台)

    绘图引擎 Windows环境下二维绘图引擎有多种选择:GDI.GDI+.DirectDraw.Qt/QPainter.Agg.Cairo.skia.Direct2D.Direct3D.OpenGL等. ...

最新文章

  1. 面试:SpringBoot中的条件注解底层是如何实现的?
  2. 近期活动盘点:工业大数据讲座、大数据自杀风险感知讲座、数据法学研讨会、海外学者短期讲学(12.3-12.13)
  3. yii2嵌入微信公众号支付
  4. 【Groovy】Groovy 扩展方法 ( 实例扩展方法配置 | 扩展方法示例 | 编译实例扩展类 | 打包实例扩展类字节码到 jar 包中 | 测试使用 Thread 实例扩展方法 )
  5. 树莓派命令行配置无线网络和SSH远程登陆
  6. 区块链BaaS云服务(14)华大BGI区块链“安全多方计算“
  7. @程序员,一文掌握 Web 应用中的图片优化技巧!
  8. c语言无线网络抓包程序,c语言实现抓包
  9. 我们玩游戏,还是游戏玩我们……
  10. 我的盖洛普“优势识别器”测试报告
  11. AutoCAD2013 以上利用AccoreConsole+ c# NetApi 批量处理图纸
  12. Flink StreamingFileSink写入hdfs,文件一直处于inprogress
  13. 计算机参数怎么写,format命令的怎样写 format命令的格式和参数【详解】
  14. GIT--使用流程规范
  15. Android全局捕获异常信息,并上传到服务器。
  16. WPF 不要给 Window 类设置变换矩阵(分析篇):System.InvalidOperationException: 转换不可逆。
  17. 单片机原理及应用之AT89S52
  18. 流式低代码编程,拖拽节点画流程图并运行
  19. java汇编工具使用
  20. 补码加法器中低位进位信息是什么?

热门文章

  1. rocksdb原理_RocksDB解析
  2. NND,都是程序员,我当学生那会儿咋没这好事
  3. 推挽输出和开漏输出的区别
  4. 老板讲述:公司有钱,我为什么不发年终奖?
  5. 计算机网络在校园中的应用方向,计算机技术在大学校园网络中的应用
  6. android音频视频播放器
  7. 一文学懂过滤器和监听器
  8. 网易云音乐听歌量爬虫(免登陆版)
  9. 中国移动5G消息开发者社区第10期5G消息云课堂 | 直播回放已上线
  10. 白帽子讲Web安全——服务端安全