BMP全称(BITMAP)是微软WINDOWS系统默认使用的一种通用图片数据存储格式,特点是结构清晰,解析简单和多平台支持广泛。其文件结构由文件头结构体,文件信息头结构体,调色板(可选),以及图片数据(压缩编码可选)组成。一个BMP文件的解析,从二进制低位开始,层层递进,每一层包含了下一层的信息,非常适合C语言进行操作。
首先介绍一下从BMP文件二进制数据前两字节,一般为‘B’ 'M'的ASCII码,其他的标识在微软未公开的OS使用,我们暂时忽略。
【1】首先读取一张BMP图像的头两个字节,一般情况下是0x42 0x4D,就是‘B’, 'M'的ASCII码,如果是两个字节一起读取就是0x4D42(lsb),十进制是19778。
【2】紧跟着就是12字节的文件头包含文件大小,有无调色板以及图像数据在偏移多少字节可以获取。
【3】在12字节的文件头后面是BMP的信息头文件。一共40字节,包含着BMP图像的宽,高,单像素大小,压缩模式,分辨率尺寸等具体的图像信息。
【4】如果有调色板的情况下在这个40字节的信息头文件之后直接存储调色板信息,每个调色板是4个字节,分别是B,G,R,A的映射值。
【5】最后的存储空间就是图像数据了,需要注意几个地方。第一,图像是按行反着存储的,第一行被存储到了最后一行,第二行存储在了最后第二行,以此类推。第二,图像如果是多通道的按BGRA方式存储。比如24位的图像单像素尺寸是已BGR顺序存储的。第三,图像每一行的大小必须是四字节对齐,比如8位图像511x511的BMP图像,每一行是511字节的图像数据自动扩展为512字节一行,用0x00补齐。
下面是12字节文件头结构体:
上图是BMP文件头结构体
bfSize是32位的元素,表示整个BMP文件的大小,单位是字节。
bfOffBits是32位的元素,表示从BMP二进制起始地址开始偏移多少字节是图像存储数据。根据之前讲解的内容,在文件存储数据前有2字节的’BM‘,12字节的文件头,40字节的信息头和调色板(如果有的话),所以总结下来bfOffBits 为54字节时没有调色板,大于54字节时,多出来的字节数就是调色板大小。
下面是40字节的信息头
其中biSize是整个信息头的大小为40字节
biWidth和biHeight代表了图像的像素分辨率,比如640x480
biPlanes这里默认为1,大部分情况都是1个plane。
biBitCount代表单个像素的位数,比如24是三字节的像素一般是BGR,32位一般是BGRA。
biCompression指的是压缩模式,0代表不压缩,1代表BGR555,2代表BGR565,3是其他模式。
biSizeImage这个变量蕴含着整个图像存储文件的大小,程序可以根据这个大小开辟空间。
biXPelsPerMeter和biYPelsPerMeter是用来描述原图的真实尺寸的,可以忽略。
biClrUsed和biClrImportant可以默认设置为0.
接下来就用C语言简单实现一下BMP文件的读和存
上图展示了读取BMP图像的过程:
【1】打开文件流,读取前两个字节
FILE * fp = fopen(path,"rb");
uint16_t bfType;
fread(&bfType,sizeof(bfType),1,fp);
【2】 读取文件头和信息头,计算调色板大小
fread(bmpFileHeader_p,sizeof(BMP_FILE_HEADER),1,fp);
fread(bmpInfoHeader_p,sizeof(BMP_INFO_HEADER),1,fp);
size_t colorTableSize = bmpFileHeader_p->bfSize - bmpInfoHeader_p->biSizeImage -2
- sizeof(BMP_FILE_HEADER) - sizeof(BMP_INFO_HEADER);
printf("colorTableSize=%d\n",colorTableSize);
【3】 首先讨论没有调色板的情况,根据读到的信息头确定图像宽高和大小,开辟空间并按行反着读取到内存空间。
printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
uint32_t dataSize=bmpInfoHeader_p->biSizeImage;
*dataPtr=(uint8_t *)malloc(dataSize);
for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
{
fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
}
【4】当步骤【2】读到的调色板大小超过0,就先读取调色板,再按行反着读取图像数据,然后根据调色板的映射还原图像的色彩信息。
if(colorTableSize>0)
{
uint8_t * table = (uint8_t *)malloc(colorTableSize);
fread(table,1,colorTableSize,fp);
// for(int i=0;i<colorTableSize;i++)
// {
// printf("colortable[%3d]: %3d\n",i,table[i]);
// }
printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
uint32_t raw_dataSize=bmpInfoHeader_p->biSizeImage;
uint32_t raw_channel = channel;
channel =3;
uint32_t dataSize =bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
*dataPtr=(uint8_t *)malloc(dataSize);
uint8_t * raw=(uint8_t *)malloc(raw_dataSize);
// fread(raw,sizeof(uint8_t),raw_dataSize,fp);
for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
{
fread((raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*raw_channel,fp);
}
BMP_COLOR_TBL * tbl_array = (BMP_COLOR_TBL*)table;
for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
{
uint8_t * write_line_ptr = (*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel;
uint8_t * raw_line_ptr = (raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel;
for(uint32_t j=0;j<bmpInfoHeader_p->biWidth;j++)
{
uint8_t v = raw_line_ptr[j];
BMP_COLOR_TBL tbl_array_ = tbl_array[v];
write_line_ptr[j*channel+0] = tbl_array_.B;
write_line_ptr[j*channel+1] = tbl_array_.G;
write_line_ptr[j*channel+2] = tbl_array_.R;
}
// fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
}
free(raw);
bmpInfoHeader_p->biBitCount=channel<<3;
bmpInfoHeader_p->biSizeImage = bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
free(table);
}
【5】关闭文件流,完成BMP文件读取
fclose(fp);
以下是完整读取BMP文件函数
void readBmpFromFile(char* path,BMP_FILE_HEADER * bmpFileHeader_p,BMP_INFO_HEADER * bmpInfoHeader_p,uint8_t ** dataPtr)
{
FILE * fp = fopen(path,"rb");
uint16_t bfType;
fread(&bfType,sizeof(bfType),1,fp);
fread(bmpFileHeader_p,sizeof(BMP_FILE_HEADER),1,fp);
fread(bmpInfoHeader_p,sizeof(BMP_INFO_HEADER),1,fp);
size_t colorTableSize = bmpFileHeader_p->bfSize - bmpInfoHeader_p->biSizeImage -2 -sizeof(BMP_FILE_HEADER) - sizeof(BMP_INFO_HEADER);
printf("colorTableSize=%d\n",colorTableSize);
if(colorTableSize>0)
{
uint8_t * table = (uint8_t *)malloc(colorTableSize);
fread(table,1,colorTableSize,fp);
// for(int i=0;i<colorTableSize;i++)
// {
// printf("colortable[%3d]: %3d\n",i,table[i]);
// }
printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
uint32_t raw_dataSize=bmpInfoHeader_p->biSizeImage;
uint32_t raw_channel = channel;
channel =3;
uint32_t dataSize =bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
*dataPtr=(uint8_t *)malloc(dataSize);
uint8_t * raw=(uint8_t *)malloc(raw_dataSize);
// fread(raw,sizeof(uint8_t),raw_dataSize,fp);
for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
{
fread((raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*raw_channel,fp);
}
BMP_COLOR_TBL * tbl_array = (BMP_COLOR_TBL*)table;
for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
{
uint8_t * write_line_ptr = (*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel;
uint8_t * raw_line_ptr = (raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel;
for(uint32_t j=0;j<bmpInfoHeader_p->biWidth;j++)
{
uint8_t v = raw_line_ptr[j];
BMP_COLOR_TBL tbl_array_ = tbl_array[v];
write_line_ptr[j*channel+0] = tbl_array_.B;
write_line_ptr[j*channel+1] = tbl_array_.G;
write_line_ptr[j*channel+2] = tbl_array_.R;
}
// fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
}
free(raw);
bmpInfoHeader_p->biBitCount=channel<<3;
bmpInfoHeader_p->biSizeImage = bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
free(table);
}
else
{
printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
uint32_t dataSize=bmpInfoHeader_p->biSizeImage;
*dataPtr=(uint8_t *)malloc(dataSize);
for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
{
fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
}
}
fclose(fp);
}
存BMP文件也是类似的操作,
一下是没有调色板的BMP文件存储
void saveBmpDataToFile(uint8_t * data_p,uint32_t pixelW,uint32_t pixelH,uint32_t pixelS,char * path,int compLevel)
{
//s0 compute key parameters info
uint32_t imageSize = pixelW * pixelH * pixelS;
uint32_t bitsPerPixel = pixelS <<3;
uint32_t imageDataOffsetFromHead = 2+sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
uint32_t imageDataOffsetFromFileHeader = sizeof(BMP_INFO_HEADER);
uint32_t fileSize = 2+imageSize + sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
FILE * fp = fopen(path,"wb");
//s1 save 'B' 'M'
char Format_BM[2]={'B','M'};
fwrite(Format_BM,sizeof(char),2,fp);
//s2 save 12 bytes File Header
BMP_FILE_HEADER fileHeader;
fileHeader.bfOffBits = imageDataOffsetFromHead;
fileHeader.bfSize = fileSize;
fileHeader.bfReserved1=0;
fileHeader.bfReserved2=0;
fwrite(&fileHeader,sizeof(BMP_FILE_HEADER),1,fp);
//s3 save 40 bytes Info Header
BMP_INFO_HEADER infoHeader;
infoHeader.biSize = imageDataOffsetFromFileHeader;
infoHeader.biWidth = pixelW;
infoHeader.biHeight = pixelH;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = bitsPerPixel;
infoHeader.biCompression = 0;
infoHeader.biSizeImage = imageSize;
infoHeader.biXPelsPerMeter = 37795;
infoHeader.biYPelsPerMeter = 37795;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
fwrite(&infoHeader,sizeof(BMP_INFO_HEADER),1,fp);
//s4 save image data with inversed lines
for(uint32_t i = 0;i<pixelH;i++)
{
fwrite(data_p+(pixelH-i-1)*(pixelW*pixelS),sizeof(uint8_t),pixelW*pixelS,fp);
}
fclose(fp);
}
以下是有调色板情况下的BMP文件存储
void saveBmpDataToFile_colorTBL(uint8_t * data_p,uint32_t pixelW,uint32_t pixelH,uint32_t pixelS,uint8_t * colorTbl,uint32_t colorTblSize,char * path)
{
if(colorTblSize==0) {printf("error! colorTblSize could not be zero!!!\n");return;};
//s0 compute key parameters info
uint32_t imageSize = pixelW * pixelH * pixelS;
uint32_t bitsPerPixel = pixelS <<3;
uint32_t imageDataOffsetFromHead = 2+colorTblSize+sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
uint32_t imageDataOffsetFromFileHeader = sizeof(BMP_INFO_HEADER);
uint32_t fileSize = 2+colorTblSize+imageSize+ sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
FILE * fp = fopen(path,"wb");
//s1 save 'B' 'M'
char Format_BM[2]={'B','M'};
fwrite(Format_BM,sizeof(char),2,fp);
//s2 save 12 bytes File Header
BMP_FILE_HEADER fileHeader;
fileHeader.bfOffBits = imageDataOffsetFromHead;
fileHeader.bfSize = fileSize;
fileHeader.bfReserved1=0;
fileHeader.bfReserved2=0;
fwrite(&fileHeader,sizeof(BMP_FILE_HEADER),1,fp);
//s3 save 40 bytes Info Header
BMP_INFO_HEADER infoHeader;
infoHeader.biSize = imageDataOffsetFromFileHeader;
infoHeader.biWidth = pixelW;
infoHeader.biHeight = pixelH;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = bitsPerPixel;
infoHeader.biCompression = 0;
infoHeader.biSizeImage = pixelW*pixelH*1;
infoHeader.biXPelsPerMeter = 37795;
infoHeader.biYPelsPerMeter = 37795;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
fwrite(&infoHeader,sizeof(BMP_INFO_HEADER),1,fp);
//s4 save color table
fwrite(colorTbl,sizeof(uint8_t),colorTblSize,fp);
//s5 save image data with inversed lines
for(uint32_t i = 0;i<pixelH;i++)
{
fwrite(data_p+(pixelH-i-1)*(pixelW*pixelS),sizeof(uint8_t),pixelW*pixelS,fp);
}
fclose(fp);
}
该工程已经同步到GIHUB,
下载地址:
https://github.com/leonard73/LeoCPorintg.git
视频讲解地址: