关于BMP
2016-06-03 09:35
337 查看
关于BMP位图的资料网上有很多,内容也比较基础。本文实现BMP位图的读取、显示、保存,并对一些重要的问题进行说明(包括字节对齐、内存中的存储顺序、调色板)。
BMP文件共包括文件头、信息头、调色板(位深<=8的图像含有此项)、位图数据四大部分:
![](https://img-blog.csdn.net/20160603091228098)
各部分的具体说明可以参考[1]。
下面是位图的读取、显示、保存实现的主体代码。(完整工程下载:Bmptest)
界面:
![](https://img-blog.csdn.net/20160603092202260)
关于四字节对齐:
Windows为了存取的效率,规定位图存储时必须满足一行为4字节的整数倍。我们处理位图时通常需要求取一行对应的字节数,需要使用如下公式:
![](https://img-blog.csdn.net/20160603092625647)
另一个常用的公式但并不适用于位深为1、4的位图:
![](https://img-blog.csdn.net/20160603092634616)
原因在于第二个公式是以字节为单位考虑的,而第一个公式以位为单位考虑。
关于图像存储顺序:
位图信息头BITMAPINFOHEADER中的biHeight不仅体现位图高度,还标记此位图的存储方式。对于一幅位图:
![](https://img-blog.csdn.net/20160603092651308)
若biHeight>0,则内存中存储顺序如下,在该模式下,内存中第一字节实际对应位图的左下角,大部分位图都按此方式存储。
![](https://img-blog.csdn.net/20160603092707012)
若biHeight<0,则内存中存储顺序如下,内存第一字节对应位图左上角。
![](https://img-blog.csdn.net/20160603092724117)
此类情况模式常用的场合是:用户自己产生一幅图像,譬如在数据采集系统中常常需要将数据进行图像显示。这时用户需要开辟一块内存并按一定方式填充该内存。由于顺序存储方式对用户来说更加直观,操作更加方便,所有常使用第二种模式。这时,在显示和保存位图时将BITMAPINFOHEADER中的biHeight设为负数即可。
关于索引图像
位深<=8位的位图才有调色板。本在编程时犯过一个错误,就是误认为8位深度的索引图调色板中就含有256(即2^8)种颜色,在读取调色板数据时若按此长度读会导致最终显示图像时发生偏移。后发现调色板颜色数可以是[2,256]范围内的任何值。
如在PS中(图像—》模式—》索引颜色)可以设置索引颜色数20:
![](https://img-blog.csdn.net/20160603092752477)
实际经测试可以发现调色板中包含21项:
0:( 20, 50, 26)
1:( 45, 77, 44)
2:( 9, 19, 8)
3:( 12, 40, 8)
4:( 19, 61, 8)
5:( 72,107, 61)
6:( 37, 82, 20)
7:( 60,110, 32)
8:(104,127, 88)
9:( 90,139, 51)
10:(135,160, 86)
11:( 45, 48, 18)
12:(174,171,134)
13:( 85, 68, 39)
14:(245,195,163)
15:(246,127, 75)
16:(129, 77, 56)
17:(241, 62, 22)
18:(125, 28, 11)
19:(173, 20, 9)
20:( 0, 0, 0)
另外一点,调色板类型RGBQUAD定义为:
typedef structtagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
即每一项四字节表示,每一字节分别表示R、G、B、A分量。这一点是重要的,也就是说在自定义调色板数据时,不管位深是1、4、8,调色板中每一分量都是在[0,255]间变化,而与位深大小无关。关于索引图像有个帖子可以参考一下[2]。
参考:
[1]http://www.cnblogs.com/xiehy/archive/2011/06/07/2074405.html
[2]http://bbs.csdn.net/topics/110048102
BMP文件共包括文件头、信息头、调色板(位深<=8的图像含有此项)、位图数据四大部分:
各部分的具体说明可以参考[1]。
下面是位图的读取、显示、保存实现的主体代码。(完整工程下载:Bmptest)
CString Filename; CStatic Picture; long Width ; long Height ; unsigned short BitCount; long Stride; unsigned char* Img; void OpenBmp(); void SaveBmp();
//打开位图 void CBmptestDlg::OpenBmp() { CRect zcRect; Picture.GetClientRect(&zcRect); CDC* pDC=Picture.GetDC(); BITMAPFILEHEADER header;//文件头 BITMAPINFOHEADER infoheader;//信息头 BITMAPINFO *bitmapinfo=NULL; FILE *fp=fopen(Filename,"rb"); if(fp==NULL){return;} fread(&header,sizeof(BITMAPFILEHEADER),1,fp); if(header.bfType != 0x4D42){return;} fread(&infoheader,sizeof(BITMAPINFOHEADER),1,fp); Width = infoheader.biWidth; Height = infoheader.biHeight>0?infoheader.biHeight:-infoheader.biHeight; BitCount = infoheader.biBitCount; Stride=((Width*BitCount+31)>>5)<<2;//一行字节数,4字节对齐 int Imgsize=Stride*Height; if (Img!=NULL){free(Img);} Img=(unsigned char*)malloc(Imgsize); if(BitCount<=8) { int Palettesize=header.bfOffBits-sizeof(BITMAPFILEHEADER)-sizeof(BITMAPINFOHEADER);//不能PaletteLen=1<<biBitCount,因调色板大小可在[2,256]取值 RGBQUAD *Palette=(RGBQUAD*)malloc(Palettesize); fread(Palette,Palettesize,1,fp); bitmapinfo=(BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER)+Palettesize); bitmapinfo->bmiHeader=infoheader; memcpy(bitmapinfo->bmiColors,Palette,Palettesize); fread(Img,Imgsize,1,fp); free(Palette); } if(BitCount>=16) { bitmapinfo=(BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER)); bitmapinfo->bmiHeader=infoheader; fread(Img,Imgsize,1,fp); } SetStretchBltMode(pDC->m_hDC,COLORONCOLOR); StretchDIBits(pDC->m_hDC,0,0,zcRect.Width(),zcRect.Height(),0,0,Width,Height,Img,bitmapinfo,DIB_RGB_COLORS,SRCCOPY); ReleaseDC(pDC); free(bitmapinfo); fclose(fp); return; } //保存位图 //常见需求是由位图数据、宽、高、位深将其保存为位图,故此函数只考虑8位灰度,16\24\32彩色位图 void CBmptestDlg::SaveBmp() { if(BitCount<8)return; if(Img==NULL)return; FILE*fp=fopen(Filename,"wb"); if(fp==NULL)return; BITMAPFILEHEADER hearder; BITMAPINFOHEADER infohearder; int ImgSize=Stride*Height; if (BitCount==8) { int PaletteSize=sizeof(RGBQUAD)*256; hearder.bfType=0X4D42; hearder.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+PaletteSize+ImgSize;//文件总大小 hearder.bfReserved1=0; hearder.bfReserved2=0; hearder.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+PaletteSize; fwrite(&hearder,sizeof(BITMAPFILEHEADER),1,fp); infohearder.biSize=sizeof(BITMAPINFOHEADER); infohearder.biWidth=Width; infohearder.biHeight=Height;//倒序 //infohearder.biHeight=-Height;//顺序 infohearder.biPlanes=1; infohearder.biBitCount=BitCount; infohearder.biCompression=BI_RGB; infohearder.biSizeImage=ImgSize; infohearder.biXPelsPerMeter = 0; infohearder.biYPelsPerMeter = 0; infohearder.biClrUsed = 0; infohearder.biClrImportant = 0; fwrite(&infohearder,sizeof(BITMAPINFOHEADER),1,fp); RGBQUAD * palette=(RGBQUAD*)malloc(PaletteSize); for (int i=0;i<256;i++) //这里针对8位灰度图 { palette[i].rgbRed=palette[i].rgbGreen=palette[i].rgbBlue=i; palette[i].rgbReserved=0; } fwrite(palette,PaletteSize,1,fp); fwrite(Img,ImgSize,1,fp); } if(BitCount>=16) { hearder.bfType=0X4D42; hearder.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+ImgSize;//文件总大小 hearder.bfReserved1=0; hearder.bfReserved2=0; hearder.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); fwrite(&hearder,sizeof(BITMAPFILEHEADER),1,fp); infohearder.biSize=sizeof(BITMAPINFOHEADER); infohearder.biWidth=Width; infohearder.biHeight=Height;//倒序 //infohearder.biHeight=-Height;//顺序 infohearder.biPlanes=1; infohearder.biBitCount=BitCount; infohearder.biCompression=BI_RGB; infohearder.biSizeImage=ImgSize; infohearder.biXPelsPerMeter = 0; infohearder.biYPelsPerMeter = 0; infohearder.biClrUsed = 0; infohearder.biClrImportant = 0; fwrite(&infohearder,sizeof(BITMAPINFOHEADER),1,fp); fwrite(Img,ImgSize,1,fp); } fclose(fp); }
界面:
关于四字节对齐:
Windows为了存取的效率,规定位图存储时必须满足一行为4字节的整数倍。我们处理位图时通常需要求取一行对应的字节数,需要使用如下公式:
另一个常用的公式但并不适用于位深为1、4的位图:
原因在于第二个公式是以字节为单位考虑的,而第一个公式以位为单位考虑。
关于图像存储顺序:
位图信息头BITMAPINFOHEADER中的biHeight不仅体现位图高度,还标记此位图的存储方式。对于一幅位图:
若biHeight>0,则内存中存储顺序如下,在该模式下,内存中第一字节实际对应位图的左下角,大部分位图都按此方式存储。
若biHeight<0,则内存中存储顺序如下,内存第一字节对应位图左上角。
此类情况模式常用的场合是:用户自己产生一幅图像,譬如在数据采集系统中常常需要将数据进行图像显示。这时用户需要开辟一块内存并按一定方式填充该内存。由于顺序存储方式对用户来说更加直观,操作更加方便,所有常使用第二种模式。这时,在显示和保存位图时将BITMAPINFOHEADER中的biHeight设为负数即可。
关于索引图像
位深<=8位的位图才有调色板。本在编程时犯过一个错误,就是误认为8位深度的索引图调色板中就含有256(即2^8)种颜色,在读取调色板数据时若按此长度读会导致最终显示图像时发生偏移。后发现调色板颜色数可以是[2,256]范围内的任何值。
如在PS中(图像—》模式—》索引颜色)可以设置索引颜色数20:
实际经测试可以发现调色板中包含21项:
0:( 20, 50, 26)
1:( 45, 77, 44)
2:( 9, 19, 8)
3:( 12, 40, 8)
4:( 19, 61, 8)
5:( 72,107, 61)
6:( 37, 82, 20)
7:( 60,110, 32)
8:(104,127, 88)
9:( 90,139, 51)
10:(135,160, 86)
11:( 45, 48, 18)
12:(174,171,134)
13:( 85, 68, 39)
14:(245,195,163)
15:(246,127, 75)
16:(129, 77, 56)
17:(241, 62, 22)
18:(125, 28, 11)
19:(173, 20, 9)
20:( 0, 0, 0)
另外一点,调色板类型RGBQUAD定义为:
typedef structtagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
即每一项四字节表示,每一字节分别表示R、G、B、A分量。这一点是重要的,也就是说在自定义调色板数据时,不管位深是1、4、8,调色板中每一分量都是在[0,255]间变化,而与位深大小无关。关于索引图像有个帖子可以参考一下[2]。
参考:
[1]http://www.cnblogs.com/xiehy/archive/2011/06/07/2074405.html
[2]http://bbs.csdn.net/topics/110048102
相关文章推荐
- JS Array创建及concat()split()slice()的使用方法
- Android中Glide(加载图片)的使用
- c#的委托(3)之多重委托
- JQuery的ajax
- 学术诚信与职业道德
- numpy数据类型
- HTML打印方法总结
- 对红黑树的理解【增加节点篇】
- #28 – DispatcherObject
- linux系统内核升级实例
- Runtime实践
- Objective-C 数字对象 (NSNumber)
- c#的委托(2)之调用实例化方法
- jQuery.validate 实现点submit跳过验证
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- 实习入职第十四天:stop called in state 4
- js框架jquery实现灯光圆盘抽奖程序活动特效
- SQL Server时间戳功能与用法详解
- c#的委托(1)之调用静态方法
- 简易发短信