« Bmp文件的结构与基本操作(逐像素印屏版)基于亮度的图像二值化处理 »

认识HBITMAP与Bmp操作(整内存拷贝版)

在上一篇里谈及了BMP文件结构的一些要点以及基于逐像素印屏版的BMP文件操作,本篇将涉及以直接Blit内存的技术显示BMP图片于Windows窗口的“快速版本”,这委实需要花时间去寻找资料,理解,应用,发现问题,除BUG。但是这不正是学习的乐趣么?——ZwqXin.com 上篇见:
Bmp文件的结构与基本操作(逐像素印屏版)

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/image-processing/bmp-operate-copy-memory.html

要知道,窗口的那个循环每次都要把像素一个一个打印到屏幕窗口里(通过简单地SetPixel),你移动一下窗口都会感受到那彻骨的延迟:怎么可以这么慢!要是我去计算FPS,那可真可能是只有几帧/秒。但是我的WIN32得依附于那个基于帧的循环啊!惟有用别的方法去处理BMP数据了。

Blit这个词给我的印象很深刻。首先是glBlitFramebufferEXT(见:OpenGL怎样近似进行同时到FBO和屏幕的渲染)见识这种技术的厉害,其次是WINDOWS游戏编程大师技巧第6章里提到DIRECTDRAW的blit技术。恩,其实都是一样的东西(这个......或许是吧)。预先把希望屏幕窗口显示的内容放在一块跟屏幕等大内存里,需要显示的时候直接把这块内存的数据一次过映射到屏幕......在读入BMP时,BMP数据的保存位置跟上篇不一样,不再是一个COLORREF的数组了,而是一个专门被指名要做Blit的内存,这都在BMP的载入过程完成——对一次BMP载入只执行一次。在WIN32程序循环里要显示图片的时候,直接从该内存BLIT出像素数据到屏幕上就好了。

恩,说起来简单,做起来不那么容易。指名?我能办到?不太可能吧。所谓的指名,还是得交给WINDOWS来做。的确,又要沉浸在WIN32的API里了……HBITMAP,跟什么HPEN啊HBRUSH啊的一样,是一种Object(WINDOWS里的对象)的句柄,干脆叫位图句柄。如果我们把我们的BITMAP_FILE转变为一个能用HBITMAP描述的(WINDOWS认得的)对象,我们就有方法用WIN32的BitBlt或StretchBlt函数API,来把HBITMAP描述的对象所在内存区域拷贝到屏幕。

首先,跳过前戏:

  1. bool OperateBMP::LoadBmp(const char *filename)
  2. {
  3.  
  4.     ifstream fileBmp;
  5.     fileBmp.open(filename,ios::binary);
  6.  
  7.     if (!fileBmp)
  8.     {
  9.         MessageBox(NULL, "无法打开喔", "XinXin's WinMain", MB_OK | MB_ICONINFORMATION);
  10.         fileBmp.close();
  11.         return false;
  12.     }
  13.     
  14.  
  15.    fileBmp.read( (char*)&bitmap.bitmapheader, sizeof(BITMAPFILEHEADER) );
  16.  
  17.    if(bitmap.bitmapheader.bfType != 0x4D42)
  18.    {
  19.         fileBmp.close();
  20.         MessageBox(NULL, "不是0x4D42-BMP", "XinXin's WinMain", MB_OK | MB_ICONINFORMATION);
  21.         return false;
  22.    }
  23.  
  24.    fileBmp.read( (char*)&bitmap.bitmapinfoheader, sizeof(BITMAPINFOHEADER) );
  25.    BmpBit = bitmap.bitmapinfoheader.biBitCount;
  26.  
  27.    if(bitmap.bitmapinfoheader.biSizeImage > 0)
  28.        BmpDataSize = bitmap.bitmapinfoheader.biSizeImage;
  29.    else
  30.        BmpDataSize = bitmap.bitmapinfoheader.biWidth * 
  31. bitmap.bitmapinfoheader.biBitCount * bitmap.bitmapinfoheader.biHeight;
  32. //(TO BE CONTINUED)

因为基于与设备有关(<=8BIT时需要)或无关(>8BIT时需要),建立对应HBITMAP的方法不同,我先说后一种情况:

  1. (接TO BE CONTINUED处)
  2.  
  3.    fileBmp.seekg((BmpDataSize)*(-1), ios::end);
  4.  
  5.    bitmap.buffer = new UCHAR[BmpDataSize];
  6.    memset(bitmap.buffer,0,BmpDataSize);
  7.  
  8.     BmpDC = CreateCompatibleDC(DrawDeviceHandler);
  9.  
  10.   //if  BmpBit > 8
  11.     if(bitmap.buffer != NULL)bitmap.buffer = NULL;  
  12.     mBMP = CreateDIBSection(BmpDC, (BITMAPINFO *)&bitmap.bitmapinfoheader, 
  13.     DIB_RGB_COLORS, (void **)&bitmap.buffer, NULL, 0);
  14.  
  15.    fileBmp.read( (char*)bitmap.buffer, bitmap.bitmapinfoheader.biSizeImage );
  16.  
  17.    fileBmp.close();
  18. ....//主要的载入操作完毕
  19. }
  20. //上面用到主要的的成员变量:
  21.     BITMAP_FILE bitmap;
  22.     HDC BmpDC;
  23.     HBITMAP mBMP;
  24.  
  25.     HDC  DrawDeviceHandler; 

看见了吗?在读入BMP数据区的数据之前,建立一个基于内存的DC:BmpDC,它兼容于我们要显示BMP图像的屏幕窗口(DrawDeviceHandler是窗口客户区DC哦)。然后在我们自己的BITMAP_FILE 的数据区被填充数据前,用CreateDIBSection建立一个HBITMAP。由它创建的HBITMAP描述的是一个DIB(与设备无关,独立的一个BITMAP),所需要的参数不言自明,其中倒数第3个就是数据区的“起始地址的地址”了。记住,只有先建立了这个DIB的Section,之后填充的数据才有效,所以对设备无关之DIB要在CreateDIBSection后才能加载真正的数据!(我可是绕了弯,现在你看到这篇拙作希望你能少绕弯~)之后,在显示部分SelectObject(BmpDC, mBMP)后用BitBlt这一个函数就能搞掂传图的工作。

DIB与DDB,可以参考这里。我的理解就是,前者独立于你的系统设备,就如同一张BMP格式的图片一样,你传送到谁的电脑上,他/她的电脑设备如何不要紧,只要他有打开BMP的软件(例如XP自带的画图程序)就能查看这张BMP,而且不会看到有什么不同,因为数据是一样的。而DDB(设备相关图)则依赖于系统设备,换言之,它不能离开设备而独立存在,是一种WINDOWS内部对一堆像素颜色信息的抽象表示。对于DIB(WINDOWS可直接操作的位图结构),我们用CreateDIBSection建立而返回一个HBITMAP描述的OBJECT;对于DDB,我们用CreateDIBitmap来创建(事实上我也觉得它这名字不好,既然创建DDB为什么还标个DIB上去捏~)。

8位位图有一个调色板,调色板这东西其实很复杂。我的理解就是,大体来说,除了图片内置的调色板外,还有每个绘图环境(DC)都有属于它的一个逻辑调色板,然后系统自己也有一个。上篇说过,非灰度图的8BIT图片数据区里那个“像素对应之BYTE”对应着调色板数组,数组每个元素标识一种颜色。问题在于“颜色”两字,这里正确的说法是“颜色值”,譬如(25,255,0)。但是,系统怎么会认得调色板里的(25,255,0)就是黄色?之前上篇里做法是把颜色转移到一个COLORREF数组里,然后当你通过窗口DC调用RGB(*,*,*)的时候,系统会认得RGB宏,认得COLORREF(它自己的数据结构嘛);在之前的8位以上BMP里,DIB与系统无关,什么调色板它不认识,直接跟系统对话了:我要XXX颜色(我要说的是,这只是表面看上去如此而已,背地里干了什么勾当呢?阴笑)。但是8BIT图(或以下)的调色板没有那个能耐,它需要在映射到屏幕窗口DC后,由该DC的逻辑调色板去跟系统调色板通话,请求系统调色板“放权”,以获得系统规定的正确颜色。(具体请参考:http://cs.ccnu.edu.cn/datastruct/download/waiweikecheng/VC/chap11_1.htm

ZwqXin大费周折后,8BIT图象的正确载入与显示弄出来了:

  1. //(接TO BE CONTINUED处)
  2. //if   BmpBit <= 8
  3.        fileBmp.read( (char*)bitmap.palette, 256*sizeof(PALETTEENTRY) );
  4.  
  5.        LOGPALETTE *logPal;
  6.        logPal=(LOGPALETTE*)new BYTE[sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*256];    
  7.        logPal->palVersion = 0x300;
  8.        logPal->palNumEntries = 256;
  9.        for (int i = 0;  i < logPal->palNumEntries;  i++)
  10.          logPal->palPalEntry[i]   = bitmap.palette[i];
  11.  
  12.        mPalette = CreatePalette(logPal); 
  13.  
  14.  
  15.        HPALETTE oldpal = NULL;
  16.        if(mPalette)
  17.        {
  18.         oldpal = SelectPalette(DrawDeviceHandler, mPalette, FALSE);
  19.         RealizePalette(DrawDeviceHandler);
  20.        }
  21.  
  22.  
  23.    fileBmp.seekg((BmpDataSize)*(-1), ios::end);
  24.  
  25.    bitmap.buffer = new UCHAR[BmpDataSize];
  26.    memset(bitmap.buffer,0,BmpDataSize);
  27.  
  28.  
  29.     BmpDC = CreateCompatibleDC(DrawDeviceHandler);
  30.    fileBmp.read( (char*)bitmap.buffer, bitmap.bitmapinfoheader.biSizeImage );
  31.  
  32.        mBMP = CreateDIBitmap(DrawDeviceHandler,&bitmap.bitmapinfoheader,CBM_INIT,bitmap.buffer,(BITMAPINFO*)&bitmap.bitmapinfoheader, DIB_RGB_COLORS);
  33.     
  34. fileBmp.close();
  35. ....//主要的载入操作完毕
  36. }
  37. //上面用到主要的的成员变量:
  38.     BITMAP_FILE bitmap;
  39.     HDC BmpDC;
  40.     HBITMAP mBMP;
  41.     HPALETTE mPalette;
  42.  
  43.     HDC  DrawDeviceHandler; 

步骤是这样的:我们读入BMP文件的调色板,然后根据它建立一个逻辑调色板结构(LOGPALETTE),也就是要给窗口DC看的那样东西(创建过程就是给LOGPALETTE结构填充信息),HPALETTE mPalette同样是个描述调色板OBJECT的句柄(调色板句柄)——我们通过CreatePalette(LOGPALETTE  logpal)函数建立一个实际的WINDOWS认识的逻辑调色板OBJECT。SelectPalette把逻辑调色板选入到要使用它的DC中(这里就是我们的当前的屏幕窗口DrawDeviceHandler啦),然后RealizePalette把该逻辑调色板实现到系统调色板中(跟系统交涉)。

之后的步骤就跟建立DIB差不多了。不过用的是建立设备无关图DDB的CreateDIBitmap,参数也是不言自明的,但注意这次要把数据读入放在建立DDB之前哦,不然它不知道具体的要用到调色板的哪些颜色的。对了,建立的是DDB口牙,这BitBlt呀StretchBlt的能读它咩?其实在读之前把BMP位图的内存DC和窗口屏幕DC都匹配这个逻辑调色板mPalette就万无一失了。诶?不是之前已经匹配过了吗?是的,所以我去掉这步也还能正确读8BIT图。但是对我这种小菜鸟来说,万一嘛万一....

  1.  void OperateBMP::ShowBmp(int oriXpos, int OriYpos, int width, int height)
  2. if(BmpBit <= 8)
  3.           {
  4.               HPALETTE OldPal1 = NULL;
  5.               HPALETTE OldPal2 = NULL;
  6.               if (mPalette)
  7.               {
  8.               OldPal1 = SelectPalette(BmpDC, mPalette, FALSE);
  9.               OldPal2 = SelectPalette (DrawDeviceHandler, mPalette, FALSE);
  10.               }
  11.           }
  12.  
  13.             SelectObject(BmpDC, mBMP);
  14.           BitBlt(DrawDeviceHandler, oriXpos, OriYpos, IMAGE_Width, IMAGE_Height, BmpDC, 0,0, SRCCOPY);
  15. }

这里的ShowBmp在客户区指定起点,尺寸(最好是原图尺寸,原图过大客考虑换用StretchBlt传图,能调节BLIT的目标大小哦)显示图片——把它放WIN32循环里吧!

保存步骤跟上篇差不多的(注意8BIT图要保存埋原调色板),不重复了。好吧,我好累,好累,真的累死了.....

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/image-processing/bmp-operate-copy-memory.html

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.