bmp文件格式
bmp文件大体上分为四个部分:
bmp文件构成 |
位图文件头BMPFileHeader |
位图信息头BMPInfoHeader |
调色板Palette |
实际的位图数据bmpDate |
第一部分为位图文件头,长度固定,为14个字节。
// BMP 文件头结构体14字节
typedef struct {
S8 signature[2]; // 文件标识符,必须为 "BM"
S32 fileSize; // 文件大小(字节)
S16 reserved1; // 保留字段1
S16 reserved2; // 保留字段2
S32 dataOffset; // 数据段相对于文件头的偏移量(字节)
} BMPFileHeader;
第二部分为位图信息头,长度固定,为40个字节。
// BMP 信息头结构体40字节
typedef struct {
S32 headerSize; // 信息头大小(字节)
S32 width; // 图片宽度(像素)
S32 height; // 图片高度(像素)
S16 planes; // 图片位平面数,必须为1
S16 bitsPerPixel; // 每个像素的位数
S32 compression; // 图像压缩类型
S32 imageSize; // 压缩图像大小(字节)
S32 xPixelsPerMeter; // 水平分辨率(像素/米)
S32 yPixelsPerMeter; // 垂直分辨率(像素/米)
S32 colorsUsed; // 使用的颜色数
S32 importantColors; // 重要颜色数
} BMPInfoHeader;
第三部分为调色板
真彩图像不需要调色版,bitmapInfoHeader后直接是数据。
第四部分为实际图像数据
对于用到调色版的位图,图像数据就是该像素颜在调色板中的索引值。对于真彩图,图像数据就是实际的R、G、B值。
注意事项:
1.在写文件头和信息头结构体的时候需要重新设置以字节对齐方式。例如,对于4字节对齐的结构体,在每个成员之间都会有4字节的填充。然而,有些时候我们需要对结构体进行精确的控制,使其占用的内存空间更小或者与其他系统进行交互。所以采取以下代码进行设置,否则会出现文件头和信息头的总大小为56字节。
#pragma pack(push, 1) // 以字节对齐方式为1
// BMP 文件头结构体14字节
typedef struct {
...
} BMPFileHeader;
// BMP 信息头结构体40字节
typedef struct {
...
} BMPInfoHeader;
#pragma pack(pop) // 恢复默认的字节对齐方式
读取bmp文件信息并展示
以下24位bmp图像为例
定义数据结构:
#pragma once
typedef unsigned char U8;
typedef unsigned short U16;
typedef unsigned int U32;
typedef unsigned long UL32;
typedef const char C8;
typedef char S8;
typedef short S16;
typedef int S32;
typedef long SL32;
typedef void UV32;
#pragma pack(push, 1) // 以字节对齐方式为1
// BMP 文件头结构体14字节
typedef struct {
S8 signature[2]; // 文件标识符,必须为 "BM"
S32 fileSize; // 文件大小(字节)
S16 reserved1; // 保留字段1
S16 reserved2; // 保留字段2
S32 dataOffset; // 数据段相对于文件头的偏移量(字节)
} BMPFileHeader;
// BMP 信息头结构体40字节
typedef struct {
S32 headerSize; // 信息头大小(字节)
S32 width; // 图片宽度(像素)
S32 height; // 图片高度(像素)
S16 planes; // 图片位平面数,必须为1
S16 bitsPerPixel; // 每个像素的位数
S32 compression; // 图像压缩类型
S32 imageSize; // 压缩图像大小(字节)
S32 xPixelsPerMeter; // 水平分辨率(像素/米)
S32 yPixelsPerMeter; // 垂直分辨率(像素/米)
S32 colorsUsed; // 使用的颜色数
S32 importantColors; // 重要颜色数
} BMPInfoHeader;
#pragma pack(pop) // 恢复默认的字节对齐方式
若需要打开文件夹选则bmp图像数据:
U8* select_file_dialog()
{
OPENFILENAME ofn;
char file_names[MAX_PATH * 10] = ""; // 存储多个文件名的缓冲区
char* folder = NULL;
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = NULL;
ofn.lpstrFile = (LPWSTR)file_names;
ofn.nMaxFile = sizeof(file_names);
ofn.lpstrFilter = (LPCWSTR)"All Files\0*.*\0\0"; // 注意修改的部分
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
if (GetOpenFileNameA((LPOPENFILENAMEA) & ofn) == TRUE)
{
// 检查是否是单个文件
if (ofn.nFileOffset > 0)
{
//printf("选择的文件路径:%s\n", file_names);
}
}
else
{
// printf("未选择文件\n");
}
return file_names;
}
主函数程序:
int main() {
FILE* file;
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
//若需要用选则文件夹的方式打开
char * filepath=select_file_dialog();
// 打开 BMP 文件 filepath 是bmp文件的路径
fopen_s(&file, filepath, "rb");
if (file == NULL)
{
printf("Failed to open file\n");
return;
}
else
{
printf("open success\n");
}
// 读取文件头
fread(&fileHeader, sizeof(BMPFileHeader), 1, file);
if (fileHeader.signature[0] != 'B' || fileHeader.signature[1] != 'M') {
printf("不是有效的 BMP 文件。\n");
fclose(file);
return 1;
}
// 读取信息头
fread(&infoHeader, sizeof(BMPInfoHeader), 1, file);
if (infoHeader.bitsPerPixel != 24) {
printf("仅支持每个像素24位的 BMP 文件。\n");
fclose(file);
return 1;
}
// 计算行字节数(一个像素占3个字节)
int rowSize = (((infoHeader.width * infoHeader.bitsPerPixel) + 31) / 32) * 4;
// 分配存储图像数据的缓冲区
unsigned char* imageData = (unsigned char*)malloc(rowSize * infoHeader.height);
if (imageData == NULL) {
printf("内存分配失败。\n");
fclose(file);
return 1;
}
// 读取图像数据
fseek(file, fileHeader.dataOffset, SEEK_SET);
fread(imageData, rowSize * infoHeader.height, 1, file);
// 翻转图像数据
for (int i = 0; i < infoHeader.height / 2; i++) {
for (int j = 0; j < infoHeader.width * 3; j++) {
unsigned char temp = imageData[i * rowSize + j];
imageData[i * rowSize + j] = imageData[(infoHeader.height - 1 - i) * rowSize + j];
imageData[(infoHeader.height - 1 - i) * rowSize + j] = temp;
}
}
//逐个像素点打印。
for (int i = 0; i < infoHeader.height; i++)
{
for (int j = 0; j < infoHeader.width; j++)
{
U8 pixelBlue = imageData[i * rowSize + j * 3];
U8 pixelGreen = imageData[i * rowSize + j * 3 + 1];
U8 pixelRed = imageData[i * rowSize + j * 3 + 2];
// 根据像素值选择字符表示
if (pixelRed >= 200 && pixelGreen >= 200 && pixelBlue >= 200)//白色
{
printf("█");
}
else
{
printf(" ");
}
}
printf("\n");
}
getchar();
free(imageData);
return 0;
}