关于BMP
2016-06-03 09:39
232 查看
关于BMP位图的资料网上有很多,内容也比较基础。本文实现BMP位图的读取、显示、保存,并对一些重要的问题进行说明(包括字节对齐、内存中的存储顺序、调色板)。
BMP文件共包括文件头、信息头、调色板(位深<=8的图像含有此项)、位图数据四大部分:
各部分的具体说明可以参考[1]。
下面是位图的读取、显示、保存实现的主体代码。(完整工程下载:Bmptest )
//打开位图
voidCBmptestDlg::OpenBmp()
{
CRectzcRect;
Picture.GetClientRect(&zcRect);
CDC*pDC=Picture.GetDC();
BITMAPFILEHEADERheader;//文件头
BITMAPINFOHEADERinfoheader;//信息头
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字节对齐
intImgsize=Stride*Height;
if(Img!=NULL){free(Img);}
Img=(unsignedchar*)malloc(Imgsize);
if(BitCount<=8)
{
intPalettesize=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彩色位图
voidCBmptestDlg::SaveBmp()
{
if(BitCount<8)return;
if(Img==NULL)return;
FILE*fp=fopen(Filename,"wb");
if(fp==NULL)return;
BITMAPFILEHEADERhearder;
BITMAPINFOHEADERinfohearder;
intImgSize=Stride*Height;
if(BitCount==8)
{
intPaletteSize=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(inti=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定义为:
typedefstructtagRGBQUAD{
BYTErgbBlue;
BYTErgbGreen;
BYTErgbRed;
BYTErgbReserved;
}RGBQUAD;
即每一项四字节表示,每一字节分别表示R、G、B、A分量。这一点是重要的,也就是说在自定义调色板数据时,不管位深是1、4、8,调色板中每一分量都是在[0,255]间变化,而与位深大小无关。关于索引图像有个帖子可以参考一下[2]。
参考:
[1]/article/6328443.html
[2]http://bbs.csdn.net/topics/110048102
BMP文件共包括文件头、信息头、调色板(位深<=8的图像含有此项)、位图数据四大部分:
各部分的具体说明可以参考
下面是位图的读取、显示、保存实现的主体代码。(完整工程下载:
CStringFilename; CStaticPicture; longWidth; longHeight; unsignedshortBitCount; longStride; unsignedchar*Img; voidOpenBmp(); voidSaveBmp();
//打开位图
voidCBmptestDlg::OpenBmp()
{
CRectzcRect;
Picture.GetClientRect(&zcRect);
CDC*pDC=Picture.GetDC();
BITMAPFILEHEADERheader;//文件头
BITMAPINFOHEADERinfoheader;//信息头
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字节对齐
intImgsize=Stride*Height;
if(Img!=NULL){free(Img);}
Img=(unsignedchar*)malloc(Imgsize);
if(BitCount<=8)
{
intPalettesize=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彩色位图
voidCBmptestDlg::SaveBmp()
{
if(BitCount<8)return;
if(Img==NULL)return;
FILE*fp=fopen(Filename,"wb");
if(fp==NULL)return;
BITMAPFILEHEADERhearder;
BITMAPINFOHEADERinfohearder;
intImgSize=Stride*Height;
if(BitCount==8)
{
intPaletteSize=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(inti=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定义为:
typedefstructtagRGBQUAD{
BYTErgbBlue;
BYTErgbGreen;
BYTErgbRed;
BYTErgbReserved;
}RGBQUAD;
即每一项四字节表示,每一字节分别表示R、G、B、A分量。这一点是重要的,也就是说在自定义调色板数据时,不管位深是1、4、8,调色板中每一分量都是在[0,255]间变化,而与位深大小无关。关于索引图像有个帖子可以参考一下
参考:
[1]
[2]
相关文章推荐
- 构建之法阅读笔记5
- 关于学术诚信与职业道德的承诺
- Git开发笔记——搭建Git简易高效服务器
- 自动类型转换
- 浅说css的绝对定位跟相对定位
- mysql的审计功能
- leetcode-Java-15. 3Sum
- Android学习笔记六十二:Java并发编程:volatile关键字解析
- 快速排序详解
- sqlserver 数据库用户密码过期
- 报表工具-ECharts 特性介绍
- 0603 感受与承诺
- 取消单元格的点击事件
- IF ERRORLEVEL 和 IF %ERRORLEVEL% 区别
- ssm框架web.xml中filter配置问题
- java的Date日期类增加天数的静态方法。
- Lecture 5:把无限多个假设降到有限个
- Thawte SSL Web Server 多域型SSL证书
- 日期与毫秒互转(转)
- [Python笔记]第十一篇:面向对象