位图文件(BMP)的结构由文件头、信息头、调色板和像素数据几部分组成,其中文件头和信息头定义了图像的基础属性,直接在内存中操作这些结构可以避免频繁的磁盘IO,提升处理效率。位图文件头的标准长度为14字节,信息头常见的是40字节的BITMAPINFOHEADER结构,两者共同决定了位图的基本参数。

位图文件头结构定义
首先需要明确位图文件头的标准字段,按照Windows BMP格式规范,文件头包含以下字段,注意结构体需要设置按1字节对齐,避免编译器自动添加填充字节导致读取偏移错误:
#include <iostream>
#include <cstring>
#include <cstdint>
// 设置结构体1字节对齐,匹配BMP文件头的实际存储格式
#pragma pack(push, 1)
// 位图文件头结构,共14字节
typedef struct {
uint16_t bfType; // 文件类型,必须是0x4D42(即字符BM)
uint32_t bfSize; // 整个BMP文件的大小,单位字节
uint16_t bfReserved1; // 保留字段,必须为0
uint16_t bfReserved2; // 保留字段,必须为0
uint32_t bfOffBits; // 像素数据相对于文件起始位置的偏移量
} BITMAPFILEHEADER;
// 位图信息头结构,共40字节,这里使用最常见的版本
typedef struct {
uint32_t biSize; // 信息头结构的大小,固定为40
int32_t biWidth; // 图像宽度,单位像素
int32_t biHeight; // 图像高度,单位像素,正数表示倒向存储,负数表示正向存储
uint16_t biPlanes; // 目标设备平面数,必须为1
uint16_t biBitCount; // 每个像素的位数,常见值有1、4、8、16、24、32
uint32_t biCompression; // 压缩方式,0表示不压缩
uint32_t biSizeImage; // 像素数据的大小,单位字节,不压缩时可以为0
int32_t biXPelsPerMeter; // 水平分辨率,单位像素/米
int32_t biYPelsPerMeter; // 垂直分辨率,单位像素/米
uint32_t biClrUsed; // 使用的调色板颜色数,0表示使用所有调色板颜色
uint32_t biClrImportant; // 重要颜色数,0表示所有颜色都重要
} BITMAPINFOHEADER;
#pragma pack(pop)
从内存中读取位图文件头
假设我们已经将完整的BMP文件数据加载到了内存缓冲区中,可以通过指针强制转换的方式直接读取文件头结构,这种方式效率最高,不需要逐字段拷贝数据。
// 模拟内存中的BMP数据缓冲区,实际场景中可能是从文件读取或者网络接收的数据
uint8_t bmp_memory_buffer[] = {
0x42, 0x4D, // bfType: BM,对应0x4D42,小端存储
0x36, 0x00, 0x00, 0x00, // bfSize: 54字节(14+40),小端存储
0x00, 0x00, // bfReserved1
0x00, 0x00, // bfReserved2
0x36, 0x00, 0x00, 0x00, // bfOffBits: 54字节,小端存储
// 接下来是40字节的信息头数据
0x28, 0x00, 0x00, 0x00, // biSize: 40
0x02, 0x00, 0x00, 0x00, // biWidth: 2像素
0x02, 0x00, 0x00, 0x00, // biHeight: 2像素
0x01, 0x00, // biPlanes: 1
0x18, 0x00, // biBitCount: 24位色深
0x00, 0x00, 0x00, 0x00, // biCompression: 不压缩
0x00, 0x00, 0x00, 0x00, // biSizeImage: 0
0x00, 0x00, 0x00, 0x00, // biXPelsPerMeter: 0
0x00, 0x00, 0x00, 0x00, // biYPelsPerMeter: 0
0x00, 0x00, 0x00, 0x00, // biClrUsed: 0
0x00, 0x00, 0x00, 0x00 // biClrImportant: 0
};
int main() {
// 将内存缓冲区指针转换为文件头结构体指针
BITMAPFILEHEADER* file_header = (BITMAPFILEHEADER*)bmp_memory_buffer;
// 信息头起始位置是文件头之后,偏移14字节
BITMAPINFOHEADER* info_header = (BITMAPINFOHEADER*)(bmp_memory_buffer + sizeof(BITMAPFILEHEADER));
// 校验文件类型是否正确
if (file_header->bfType != 0x4D42) {
std::cout << "不是有效的BMP文件" << std::endl;
return -1;
}
std::cout << "BMP文件大小: " << file_header->bfSize << " 字节" << std::endl;
std::cout << "像素数据偏移量: " << file_header->bfOffBits << " 字节" << std::endl;
std::cout << "图像宽度: " << info_header->biWidth << " 像素" << std::endl;
std::cout << "图像高度: " << info_header->biHeight << " 像素" << std::endl;
std::cout << "位深: " << info_header->biBitCount << " 位/像素" << std::endl;
return 0;
}
修改内存中的位图文件头字段
读取到头结构之后,可以直接修改结构体中的字段,修改后的数据会同步到原始内存缓冲区中,适合需要动态修改图像属性的场景,比如调整图像尺寸、修改位深等。
int main() {
BITMAPFILEHEADER* file_header = (BITMAPFILEHEADER*)bmp_memory_buffer;
BITMAPINFOHEADER* info_header = (BITMAPINFOHEADER*)(bmp_memory_buffer + sizeof(BITMAPFILEHEADER));
// 修改图像宽度为4像素
info_header->biWidth = 4;
// 重新计算文件大小,假设24位色深,每行像素需要对齐到4字节
int line_bytes = ((info_header->biWidth * info_header->biBitCount + 31) / 32) * 4;
int pixel_data_size = line_bytes * info_header->biHeight;
file_header->bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + pixel_data_size;
file_header->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
std::cout << "修改后图像宽度: " << info_header->biWidth << " 像素" << std::endl;
std::cout << "修改后文件大小: " << file_header->bfSize << " 字节" << std::endl;
return 0;
}
注意事项
- 结构体必须设置1字节对齐,否则编译器会自动添加填充字节,导致结构大小和BMP文件头实际大小不匹配,读取到错误的数据。
- BMP文件的所有数值字段都是小端存储,在大小端不一致的平台上需要做字节序转换,上述示例默认运行在小端平台上。
- 操作内存指针时需要确保缓冲区长度足够,避免越界访问导致程序崩溃。
- 修改文件头后如果需要保存到文件,需要保证像素数据的大小和头结构中声明的参数匹配,否则图像会显示异常。