我的第一个MFC小项目(3)之 位图转换
2011-12-08 16:20 捣乱小子 阅读(3482) 评论(2) 编辑 收藏 举报关于位图的格式,文件头,信息头,颜色表,像素位等等,我在以前在《我的第一个MFC小项目(2)》有过简单的介绍,当时还操VISIO自己画图呢,不过当时真的非常非常的模糊,甚至还没有灰度图和彩图的概念。没有捧着一本书认真研究,纯粹是完成项目过程当中遇到什么不懂的就直接google...欢迎拍砖,欢迎讨论。
8位位图除了可以索引彩色图像外,也可以是灰阶图像,相信更多的是用于灰度的图像,既然有8位的灰阶图像,也就是说从白到黑分成256种渐变,那16位灰阶图像也是存在的,只不过从白到黑分成2^16种渐变;但这是一种很大的浪费我觉得,因为灰阶图像应用不是非常广,在一些专业领域或许会用到。
而如今PC下的更多的是24位的,32位的,16位的也有。一开始的时候还不知道什么是RGB,其实简单来说就是Red,Green,Blue分别用一定的位数来存储他们的值。16位以上多见RGB格式的图,16位位图图片还可分为R5G6B5,R5G5B5X1(有1位不携带信息,其实就是最高位),R5G5B5A1,R4G4B4A4 等等。
其中RGB555,BITMAPINFOHEADER信息头字段biCompression成员的值是BI_RGB,它的存储格式是:
XRRRRRGG GGGBBBBB,注意它跟24位位图没有颜色表。
24位位图更简单,它的存储格式是:
RRRRRRRR GGGGGGGG BBBBBBBB
而32位的位图,新增了一个透明度,这在XP下很常见了:
AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB
图形处理中,通常把RGB三种颜色信息称为红通道、绿通道和蓝通道,相应的把透明度称为Alpha通道。由RGB形成的图像均称做真彩色。
OK,差不多就啰嗦到这里吧。真正在写程序中遇到的问题是位图的转换:将24位真彩位图转换为8位灰阶位图。而没有上面的东西,下面的东西看起来会很吃力(还包括《我的第一个MFC小项目(2)》)。
问题描述的清楚一点吧:原图片是24位位图,而如今项目当中的绝多数操作都是基于8位位图,不可能让客户多一手准备8位的位图,自然而然转换的任务就落在了程序的头上了。
我的解决方法是这样:就用一个方法来实现这个转换的操作,而函数的参数就一个路径(让客户只提供路径,这方便很多了),在转换之后再写为另一个8位位图文件。
或许一开始拿到这样的任务,还是摸不着头脑,不知道从何下手;我画了下面的一张图,
Y是什么,Y就是明度,在灰阶位图当中,只有明度(灰阶值),而没有色度和浓度,它和RGB是有区别的,是两种不同的颜色编码方法,RGB注重的是色彩,而YUV注重的亮度。幸运的是,两者之间可以进行转换,
对,上面的第一个公式正是我们想要的,熟悉DIB格式的童鞋都应该有思路了吧,像素位里面就存着RGB,它与上面的公式能够帮助我们将24位位图转换为8位灰阶位图。
其中有个问题,就是涉及到位图的每行所占字节数的问题,在《我的第一个MFC小项目(2)》中也涉及了一点点,所以再次重申一次,Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,每行所占字节数的计算公式:((((width*32)+31)/32)*4)。
举一个简单的例子:假如一个8位灰阶位图,它的宽是31,则每一行需要31个字节存储,因为字节数必须是4的整倍数,所以应该是32字节,此时BITMAPINFOHEADER字段当中,biWidth=31,biBitCount=8,但要清楚,每行所占字节是32字节。因此在做每行转换为8位位图的时候,我们需要每个扫描行+32,而不是每个扫描行+31。
所以下面所展示的程序,看到宽度转换可以回到这里找答案。上代码吧,
void Convertto8Bit() { HANDLE hFile; //文件句柄 DWORD dwWritten; //记录以写入的字节数 hFile = CreateFile(L"F:\\1.bmp",GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); BITMAPFILEHEADER bmfh; //文件头 ReadFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL); BITMAPINFOHEADER bmif; //信息头 ReadFile(hFile,&bmif,40,&dwWritten,NULL); DWORD dwSizeImage; //源文件像素位大小 dwSizeImage = //计算 像素位 的大小 bmfh.bfSize - sizeof(BITMAPFILEHEADER) - sizeof(BITMAPINFOHEADER); BYTE * pBits = new BYTE[dwSizeImage]; ReadFile(hFile,pBits,dwSizeImage,&dwWritten,NULL); ::CloseHandle(hFile); long lSrcWidth = bmif.biWidth; //原图长与宽 long lSrcHeight = bmif.biHeight; long lLineBytes; //原图每行总字节数 long lScanWidth; //转换为8位图之后的宽度,必须是大于原图且为4的倍数 lLineBytes = ((lSrcWidth*3)/4)*4; //为了与8位位图数据对齐,原图每行总字节数也必须为4的倍数, if(lLineBytes<lSrcWidth*3) //在这里转换需要比原图每行总字节数大 lLineBytes += 4; lScanWidth = (lSrcWidth/4)*4; //8位位图的宽度必须为4的倍数,在这里转换需要比原图宽度大 if(lScanWidth<lSrcWidth) lSrcWidth += 4; DWORD dwSizeNewImage = lSrcWidth * lSrcHeight + 2; //为什么要预留两位呢 BYTE * bits = new BYTE[dwSizeNewImage]; for(int i=0; i<lSrcHeight; i++) { for(int j=0; j<lSrcWidth; j++) { BYTE color[3]; //对应RGB的红绿蓝值 DWORD dwColorTemp; //Y值,RGB转换为Y之后的值Y=0.299*R+0.587*G+0.114*B for(int s=0;s<3;s++) //一个RGB对应一个Y值 color[s]=pBits[i*lLineBytes+j*3+s]; dwColorTemp=unsigned int(color[2]*0.299+color[1]*0.587+color[0]*0.114); if(dwColorTemp>255) dwColorTemp = 255; if(dwColorTemp<0) dwColorTemp = 0; bits[i*lScanWidth+j]=(unsigned char)dwColorTemp; } } bits[dwSizeNewImage-1] = 0; bits[dwSizeNewImage-2] = 0; RGBQUAD *rgbQuad = new RGBQUAD[256]; //颜色表 for(int i=0;i<256;i++) { rgbQuad[i].rgbBlue = i; rgbQuad[i].rgbGreen = i; rgbQuad[i].rgbRed = i; rgbQuad[i].rgbReserved = 0; } //完善8位位图的文件头和信息头字段 bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD); //颜色表有256个 bmfh.bfReserved1 = 0; bmfh.bfReserved2 = 0; bmfh.bfSize = bmfh.bfOffBits + dwSizeNewImage; //size in byte of the file bmif.biBitCount = 8; //信息头中bitcounts改为8 bmif.biSizeImage = dwSizeNewImage; //写入转换后得到的8位位图 hFile=CreateFile(L"F://2.bmp",GENERIC_WRITE, FILE_SHARE_WRITE,NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); ::WriteFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL); ::WriteFile(hFile,&bmif,sizeof(BITMAPINFOHEADER),&dwWritten,NULL); ::WriteFile(hFile,rgbQuad,256*sizeof(RGBQUAD),&dwWritten,NULL); ::WriteFile(hFile,bits,dwSizeNewImage,&dwWritten,NULL); ::CloseHandle(hFile); }
童鞋们,在这里缺了很多的检测,文件大小的检测,如果位图超过4M,就不被允许了,所以一开始应该结合getfilesize来检测,还有读入数据的时候是否已经读入正确的字节数,特别是文件头和信息头;BITMAPFILEHEADER中bftype字段应该为‘BM’;文件句柄的检测等等,因为程序长,我忽略了。一个优秀程序员都不应该忘记这些,很明显菜鸟一个啊....
传送门:
我的第一个MFC小项目(2)之 初涉位图
我的第一个MFC小项目(4)之 位图转换(续)
欢迎讨论,欢迎拍砖...
捣乱小子 2011-12-08