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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现bmp图像转180度,可以按照以下步骤进行: 1. 读取bmp图像文件头和信息头,获取图像的宽度和高度,以及每个像素占用的字节数。 2. 将每行像素数据读入内存中的一个数组中。 3. 创建一个新的数组,用于存储翻转后的图像数据。 4. 遍历原始像素数组,将每行像素数据倒序存入新的数组中。 5. 将新的像素数组写入一个新的bmp图像文件中。 下面是一个C语言实现bmp图像转180度的例子: ```c #include<stdio.h> #include<stdlib.h> #include<string.h> #pragma pack(2) // 按2字节对齐 // bmp文件头 typedef struct { char bfType[2]; unsigned int bfSize; unsigned short bfReserved1; unsigned short bfReserved2; unsigned int bfOffBits; } BMPFILEHEADER; // bmp信息头 typedef struct { unsigned int biSize; unsigned int biWidth; unsigned int biHeight; unsigned short biPlanes; unsigned short biBitCount; unsigned int biCompression; unsigned int biSizeImage; unsigned int biXPelsPerMeter; unsigned int biYPelsPerMeter; unsigned int biClrUsed; unsigned int biClrImportant; } BMPINFOHEADER; int main() { FILE *fp_in, *fp_out; BMPFILEHEADER file_header; BMPINFOHEADER info_header; unsigned char *img_data, *new_img_data; int width, height, pad_size, row_size, i, j; // 打开原始bmp文件 fp_in = fopen("input.bmp", "rb"); if (fp_in == NULL) { printf("Failed to open input file!\n"); return -1; } // 读取bmp文件头和信息头 fread(&file_header, sizeof(BMPFILEHEADER), 1, fp_in); fread(&info_header, sizeof(BMPINFOHEADER), 1, fp_in); // 计算图像宽度和高度 width = info_header.biWidth; height = info_header.biHeight; // 计算每行像素数据的字节数 row_size = (info_header.biBitCount * width + 31) / 32 * 4; // 计算每行需要填充的字节数 pad_size = row_size - width * info_header.biBitCount / 8; // 分配内存用于存储原始像素数据和翻转后的像素数据 img_data = (unsigned char*)malloc(row_size * height); new_img_data = (unsigned char*)malloc(row_size * height); // 读取原始像素数据 fread(img_data, sizeof(unsigned char), row_size * height, fp_in); // 关闭原始bmp文件 fclose(fp_in); // 翻转像素数据 for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { memcpy(new_img_data + (height - i - 1) * row_size + j * info_header.biBitCount / 8, img_data + i * row_size + j * info_header.biBitCount / 8, info_header.biBitCount / 8); } } // 打开新的bmp文件 fp_out = fopen("output.bmp", "wb"); if (fp_out == NULL) { printf("Failed to open output file!\n"); return -1; } // 写入bmp文件头和信息头 fwrite(&file_header, sizeof(BMPFILEHEADER), 1, fp_out); fwrite(&info_header, sizeof(BMPINFOHEADER), 1, fp_out); // 写入翻转后的像素数据 fwrite(new_img_data, sizeof(unsigned char), row_size * height, fp_out); // 关闭新的bmp文件 fclose(fp_out); // 释放内存 free(img_data); free(new_img_data); return 0; } ``` 注意,这只是一个简单的实现,可能不支持所有bmp文件格式。在实际应用中,还需要考虑更多的细节问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值