必须先用std::ifstream以binary模式读取14字节BITMAPFILEHEADER并校验bfType为0x4D42,再读40字节BITMAPINFOHEADER并验证biSize为40,bfOffBits须≥54且小端序解析,结构体需#pragma pack(1)或手动字节提取避免对齐错误。

如何用C++读取BMP文件头的BITMAPFILEHEADER和BITMAPINFOHEADER
直接读取BMP文件头不需要第三方库,但必须严格按Windows BMP规范解析二进制结构。关键不是“能不能读”,而是“读出来是否可信”——很多代码跳过字节序校验或偏移计算,导致在不同平台或异常BMP上出错。
标准BMP文件开头是BITMAPFILEHEADER(14字节),紧跟着BITMAPINFOHEADER(40字节,常见为BI_RGB格式)。两者都使用小端序(Little-Endian),且bfOffBits字段指示像素数据起始位置,这个值必须大于等于14 + 40,否则是无效BMP。
- 用
std::ifstream以std::ios::binary模式打开文件,否则换行符会被误转 - 先读14字节到
BITMAPFILEHEADER结构体,检查bfType是否等于0x4D42(即'BM') - 再读40字节到
BITMAPINFOHEADER,验证biSize是否为40(不支持OS/2格式等变体) - 不要直接
reinterpret_cast整个文件内存块——结构体对齐可能被编译器填充,必须逐字段读或用#pragma pack(2)强制对齐
为什么fread比std::istream::read更容易出错
fread本身没问题,但C风格读取容易忽略返回值、类型转换错误或缓冲区溢出。比如把BITMAPFILEHEADER*直接传给fread,若结构体因对齐插入填充字节,实际读入的字节数就和sizeof(BITMAPFILEHEADER)不一致。
更稳妥的做法是用std::vector<char></char>分配精确大小的缓冲区,再用std::istream::read填充:
立即学习“C++免费学习笔记(深入)”;
std::ifstream f("test.bmp", std::ios::binary);
if (!f) return;
std::vector<char> buf(14);
f.read(buf.data(), 14);
if (f.gcount() != 14) /* 文件太短 */;
// 然后手动解析buf[0]~buf[13],避免结构体填充干扰
-
f.gcount()必须检查,不能只依赖!f.fail() - 字段提取推荐用位运算或
memcpy到uint16_t/uint32_t变量,而非直接指针解引用 - 例如
bfOffBits是DWORD(4字节),应组合buf[10]~buf[13]:(buf[10] | buf[11]
biWidth和biHeight为负数意味着什么
BITMAPINFOHEADER.biHeight为负数表示图像是倒序存储(top-down),即首行像素对应图像顶部;正数则是bottom-up(Windows传统BMP默认)。这直接影响后续像素扫描方向,但**不影响文件头解析本身**——只要数值可解析,就是合法BMP。
-
biWidth为负数在规范中未定义,遇到时应视为损坏或非标BMP -
biPlanes必须为1,否则不是标准RGB位图 -
biBitCount常见值为1、4、8、24、32;16需结合biCompression判断是否555/565格式 -
biCompression非BI_RGB(0)时,不能假设像素数据是线性RGB,例如BI_RLE8需解码
跨平台读取时最容易被忽略的细节
Windows下生成的BMP在Linux/macOS用C++读取时,唯一真正需要处理的是字节序——但x86/x64/ARM64主流平台都是小端,所以字段解析逻辑一致。真正坑在于文件系统对换行、路径分隔符的处理,以及BMP可能含额外信息块(如ICC profile)。
-
bfOffBits值可能大于14+40,说明存在BITMAPV4HEADER、调色板或扩展头,此时不能硬编码跳过54字节 - 某些工具导出的BMP会在
bfOffBits之后插入ICCPART块,但bfOffBits仍指向像素数据起点,这部分要跳过而非报错 - 用
std::filesystem::path构造文件路径,避免手动拼接"\"或"/" - 如果目标是快速验证BMP有效性,只需检查
bfType==0x4D42、bfOffBits>=54、biSize==40、biWidth>0、biHeight!=0
文件头只是入口,真正麻烦的是像素数据对齐(每行字节必须是4的倍数)、调色板解析、压缩格式支持——这些不在头信息里,但头里的字段决定了你接下来要不要处理它们。


















