C语言读取bmp图像数据并终端上显示

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;
}

代码终端效果展示:

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值