在上一篇里谈及了BMP文件结构的一些要点以及基于逐像素印屏版的BMP文件操作,本篇将涉及以直接Blit内存的技术显示BMP图片于Windows窗口的“快速版本”,这委实需要花时间去寻找资料,理解,应用,发现问题,除BUG。但是这不正是学习的乐趣么?——ZwqXin.com 上篇见:
Bmp文件的结构与基本操作(逐像素印屏版)
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/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描述的对象所在内存区域拷贝到屏幕。
首先,跳过前戏:
- bool OperateBMP::LoadBmp(const char *filename)
- {
- ifstream fileBmp;
- fileBmp.open(filename,ios::binary);
- if (!fileBmp)
- {
- MessageBox(NULL, "无法打开喔", "XinXin's WinMain", MB_OK | MB_ICONINFORMATION);
- fileBmp.close();
- return false;
- }
- fileBmp.read( (char*)&bitmap.bitmapheader, sizeof(BITMAPFILEHEADER) );
- if(bitmap.bitmapheader.bfType != 0x4D42)
- {
- fileBmp.close();
- MessageBox(NULL, "不是0x4D42-BMP", "XinXin's WinMain", MB_OK | MB_ICONINFORMATION);
- return false;
- }
- fileBmp.read( (char*)&bitmap.bitmapinfoheader, sizeof(BITMAPINFOHEADER) );
- BmpBit = bitmap.bitmapinfoheader.biBitCount;
- if(bitmap.bitmapinfoheader.biSizeImage > 0)
- BmpDataSize = bitmap.bitmapinfoheader.biSizeImage;
- else
- BmpDataSize = bitmap.bitmapinfoheader.biWidth *
- bitmap.bitmapinfoheader.biBitCount * bitmap.bitmapinfoheader.biHeight;
- //(TO BE CONTINUED)
因为基于与设备有关(<=8BIT时需要)或无关(>8BIT时需要),建立对应HBITMAP的方法不同,我先说后一种情况:
- (接TO BE CONTINUED处)
- fileBmp.seekg((BmpDataSize)*(-1), ios::end);
- bitmap.buffer = new UCHAR[BmpDataSize];
- memset(bitmap.buffer,0,BmpDataSize);
- BmpDC = CreateCompatibleDC(DrawDeviceHandler);
- //if BmpBit > 8
- if(bitmap.buffer != NULL)bitmap.buffer = NULL;
- mBMP = CreateDIBSection(BmpDC, (BITMAPINFO *)&bitmap.bitmapinfoheader,
- DIB_RGB_COLORS, (void **)&bitmap.buffer, NULL, 0);
- fileBmp.read( (char*)bitmap.buffer, bitmap.bitmapinfoheader.biSizeImage );
- fileBmp.close();
- ....//主要的载入操作完毕
- }
- //上面用到主要的的成员变量:
- BITMAP_FILE bitmap;
- HDC BmpDC;
- HBITMAP mBMP;
- 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图象的正确载入与显示弄出来了:
- //(接TO BE CONTINUED处)
- //if BmpBit <= 8
- fileBmp.read( (char*)bitmap.palette, 256*sizeof(PALETTEENTRY) );
- LOGPALETTE *logPal;
- logPal=(LOGPALETTE*)new BYTE[sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*256];
- logPal->palVersion = 0x300;
- logPal->palNumEntries = 256;
- for (int i = 0; i < logPal->palNumEntries; i++)
- logPal->palPalEntry[i] = bitmap.palette[i];
- mPalette = CreatePalette(logPal);
- HPALETTE oldpal = NULL;
- if(mPalette)
- {
- oldpal = SelectPalette(DrawDeviceHandler, mPalette, FALSE);
- RealizePalette(DrawDeviceHandler);
- }
- fileBmp.seekg((BmpDataSize)*(-1), ios::end);
- bitmap.buffer = new UCHAR[BmpDataSize];
- memset(bitmap.buffer,0,BmpDataSize);
- BmpDC = CreateCompatibleDC(DrawDeviceHandler);
- fileBmp.read( (char*)bitmap.buffer, bitmap.bitmapinfoheader.biSizeImage );
- mBMP = CreateDIBitmap(DrawDeviceHandler,&bitmap.bitmapinfoheader,CBM_INIT,bitmap.buffer,(BITMAPINFO*)&bitmap.bitmapinfoheader, DIB_RGB_COLORS);
- fileBmp.close();
- ....//主要的载入操作完毕
- }
- //上面用到主要的的成员变量:
- BITMAP_FILE bitmap;
- HDC BmpDC;
- HBITMAP mBMP;
- HPALETTE mPalette;
- 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图。但是对我这种小菜鸟来说,万一嘛万一....
- void OperateBMP::ShowBmp(int oriXpos, int OriYpos, int width, int height)
- {
- if(BmpBit <= 8)
- {
- HPALETTE OldPal1 = NULL;
- HPALETTE OldPal2 = NULL;
- if (mPalette)
- {
- OldPal1 = SelectPalette(BmpDC, mPalette, FALSE);
- OldPal2 = SelectPalette (DrawDeviceHandler, mPalette, FALSE);
- }
- }
- SelectObject(BmpDC, mBMP);
- BitBlt(DrawDeviceHandler, oriXpos, OriYpos, IMAGE_Width, IMAGE_Height, BmpDC, 0,0, SRCCOPY);
- }
这里的ShowBmp在客户区指定起点,尺寸(最好是原图尺寸,原图过大客考虑换用StretchBlt传图,能调节BLIT的目标大小哦)显示图片——把它放WIN32循环里吧!
保存步骤跟上篇差不多的(注意8BIT图要保存埋原调色板),不重复了。好吧,我好累,好累,真的累死了.....
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/image-processing/bmp-operate-copy-memory.html