0. BMP格式介绍
根据百度百科对BMP的定义,我们知道BMP是Bitmap(位图)的简称,是Windows操作系统中的标准图像文件格式。其特点是由于几乎不进行压缩,所以包含的图像信息较丰富,但同时也到之占用的磁盘空间较大。
1 文件格式
位图文件由4个部分组成:
- 位图头文件(bitmap-file header)
- 位图信息头(bitmap-information header)
- 颜色表(color table):使用索引来表示图像,此时颜色表就是索引与其对应的颜色之间的映射表(即通过索引值,结合颜色表,找到对应的像素信息)
- 位图数据(data bits)
上述不同的部分,有不同的数据结构,下面分别介绍前面3个部分的数据结构。
数据段名称 | 对应的Windows结构体定义 | 大小(Byte) |
bmp文件头 | BITMAPFILEHEADER | 14 |
bmp信息头 | BITMAPINFOHEADER | 40 |
颜色表 | 由颜色索引数决定 | |
bmp数据 | 由图像尺寸决定 |
2 位图头文件
本文,以上图来分析BMP图像的文件格式。在分析图像数据之前,需要强调的是BMP文件中,数据的存储方式为小端方式(little endian),即假设一个数据需要多个字节来表示,那么数据的存放字节的顺序为“低地址存放地位数据,高地址存放高位数据”。在十六进制中,一个数字占4位,因此每个字节可以存储2个十六进制数字。
因此,对于数据0x1756在内存中的存储顺序为:
位图头文件的结构体定义如下:
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
DWORD bfSize; // 文件大小 以字节为单位(2-5字节)
UINT16 bfReserved1; // 保留,必须设置为0 (6-7字节)
UINT16 bfReserved2; // 保留,必须设置为0 (8-9字节)
DWORD bfOffBits; // 从文件头到像素数据的偏移 (10-13字节)
} BITMAPFILEHEADER;
在VScode中,可以通过安装hexdump插件来以二进制的形式查看BMP图像数据。
对照结构体和文件数据可以看到:
0-1 : 0x4d42='BM',表示这是Windows支持的位图格式
2-5 : 0x000088EA表示文件大小
6-9 : 保留段,为0
A-D : 0x00000436=1078,表示从文件头到位图数据需要偏移1078个字节
【注意】利用fopen打开文件,得到文件句柄读取相应的数据时,存在一个问题就是结构体内存对齐。这个会使得后续数据的读取出现错误。
何为内存对齐具体可以参考文章:结构体成员在内存中的对齐方式
上述问题的具体表现可以通过如下的代码进行实现:
#include <stdio.h>
typedef struct tagBITMAPFILEHEADER
{
unsigned short bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件(0-4)
int bfSize; // 文件大小 以字节为单位(2-5字节) (4-8)
unsigned short bfReserved1; // 保留,必须设置为0 (6-7字节) (8-10)
unsigned short bfReserved2; // 保留,必须设置为0 (8-9字节) (10-12)
int bfOffBits; // 从文件头到像素数据的偏移 (10-13字节) (12-16)
} BITMAPFILEHEADER;
int main(int argc, char* argv[])
{
printf("sizeof(BITMAPFILEHEADER) = %lu\n", sizeof(BITMAPFILEHEADER));
return 0;
}
// sizeof(BITMAPFILEHEADER) = 16
那么如何关闭结构体的内存对齐 ,有两种方式:添加预处理指令 #pragma pack(1) 或者 __attribute__ ((packed))。这两种方式的区别在于:
- #pragma pack(1)预处理指令,是关闭整个文件的内存对齐模式
- __attribute__ ((packed))指令,则是针对指定的结构体
#include <stdio.h>
// #pragma pack(1)
typedef struct tagBITMAPFILEHEADER
{
unsigned short bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件(0-4)
int bfSize; // 文件大小 以字节为单位(2-5字节) (4-8)
unsigned short bfReserved1; // 保留,必须设置为0 (6-7字节) (8-10)
unsigned short bfReserved2; // 保留,必须设置为0 (8-9字节) (10-12)
int bfOffBits; // 从文件头到像素数据的偏移 (10-13字节) (12-16)
}__attribute__ ((packed)) BITMAPFILEHEADER;
int main(int argc, char* argv[])
{
printf("sizeof(BITMAPFILEHEADER) = %lu\n", sizeof(BITMAPFILEHEADER));
return 0;
}
// sizeof(BITMAPFILEHEADER) = 14
3 位图信息头
位图信息头的结构体定义如下:
typedef struct tagBITMAPINFOHEADER
{
unsigned int biSize; // 此结构体的大小 (14-17字节)
long biWidth; // 图像的宽 (18-21字节)
long biHeight; // 图像的高 (22-25字节)
unsigned short biPlanes; // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节)
unsigned short biBitCount; // 一像素所占的位数,一般为24 (28-29字节)
unsigned int biCompression; // 说明图象数据压缩的类型,0为不压缩。 (30-33字节)
unsigned int biSizeImage; // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节)
long biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节)
long biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节)
unsigned int biClrUsed; // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节)
unsigned int biClrImportant; // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节)
} BITMAPINFOHEADER;
4 颜色表
颜色表本质上是一个查询表,通过颜色序号来查询对应的颜色。在文件中的布局类似一个二维数组palette[N][4]. 其中4个元素分别表示对应的B,G,R和Alpha的值,每个分量占一个字节。N一般等于256。
一共有256种颜色,每个颜色占用4个字节,就是一共1024个字节,再加上前面的文件信息头和位图信息头的54个字节加起来一共是1078个字节。
5. 位图数据
位图数据中,每个像素占一个字节,以该字节的数作为颜色索引去颜色表查询相应的颜色,并将其显示出来即可。
6. 参考链接
C实现BMP转JPG 附源码_等jzy的博客-CSDN博客_c语言bmp转jpg
用c、cpp实现位图(BMP)读入写出的思路及细节 - et3_tsy - 博客园 (cnblogs.com)
C语言实现BMP图像的读写功能_C 语言_脚本之家 (jb51.net)