C 中的 .BMP 文件 - DIB 标头返回图像大小 0,即使 BMP 标头返回文件大小

Posted

技术标签:

【中文标题】C 中的 .BMP 文件 - DIB 标头返回图像大小 0,即使 BMP 标头返回文件大小【英文标题】:.BMP files in C - DIB header returns image size 0 even though the BMP header returns the file size 【发布时间】:2022-01-15 20:24:53 【问题描述】:

我正在尝试在 C 中翻转 BMP 图像。 我遇到了一个问题,当我尝试读取 BMP 标头时一切正常并且值正确,但是当我尝试读取 DIB 标头时,我得到了除图像大小(原始位图数据)之外的所有值。我得到一个零,考虑到我在 BMP 标头中获得了文件大小,这真的很奇怪。我尝试调试代码并在线查找问题,但没有太大帮助。

这是我的代码:

#include <stdlib.h>

typedef unsigned int int32;
typedef unsigned short int int16;
typedef unsigned char char8;

struct BITMAP_header 
    char name[2]; // BM
    int32 size;
    int garbage; // ?
    int32 image_offset; //offset from where the image starts in the file
;

struct DIB_header 
    int32 header_size;
    int32 width;
    int32 height;
    int16 colorplanes;
    int16 bitsperpixel;
    int32 compression;
    int32 image_size;
    int32 temp[4];
;

struct RGB

    char8 blue;
    char8 green;
    char8 red;
;

struct Image 
    struct RGB** rgb;
    int height;
    int width;
;

struct Image readImage(FILE *fp, int height, int width) 
    struct Image pic;

    pic.rgb = (struct RGB**)malloc(height * sizeof(void*)); // pointer to a row  of rgb data (pixels)
    pic.height = height;
    pic.width = width;

    for (int i = height-1; i >=0 ; i--)
    
        pic.rgb[i] = (struct RGB*)malloc(width * sizeof(struct RGB)); // allocating a row of pixels
        fread(pic.rgb[i], width, sizeof(struct RGB), fp);
    
    
    return pic;


void freeImage(struct Image pic) 
    for (int i = pic.height -1; i>= 0; i--)
    
        free(pic.rgb[i]);
    
    free(pic.rgb);


void createImage(struct BITMAP_header header, struct DIB_header dibheader, struct Image pic) 
    FILE* fpw = fopen("new.bmp", "w");
    if (fpw == NULL) 
        return 1;
    

    fwrite(header.name, 2, 1, fpw);
    fwrite(&header.size, 3 * sizeof(int), 1, fpw);

    fwrite(&dibheader, sizeof(struct DIB_header), 1, fpw);

    int count = 0;
    for (int i = pic.height - 1; i >= 0; i--) 
        fwrite(pic.rgb[count], pic.width, sizeof(struct RGB), fpw);
        count++;
    

    fclose(fpw);


int openbmpfile() 
    FILE* fp = fopen("C:\\Users\\User\\Downloads\\MARBLES.BMP", "rb"); // read binary
    if (fp == NULL) 
        return 1;
    

    struct BITMAP_header header;
    struct DIB_header dibheader;

    fread(header.name, 2, 1, fp); //BM
    fread(&header.size, 3 * sizeof(int), 1, fp); // 12 bytes

    printf("First two characters: %c%c\n", header.name[0], header.name[1]);
    if ((header.name[0] != 'B') || (header.name[1] != 'M')) 
        fclose(fp);
        return 1;
    

    printf("Size: %d\n", header.size);
    printf("Offset: %d\n", header.image_offset);

    fread(&dibheader.header_size, sizeof(struct DIB_header) , 1, fp);
    printf("Header size: %d\nWidth: %d\nHeight: %d\nColor planes: %d\nBits per pixel: %d\nCompression: %d\nImage size: %d\n",
        dibheader.header_size, dibheader.width, dibheader.height, dibheader.colorplanes, dibheader.bitsperpixel,
        dibheader.compression, dibheader.image_size);

    if ((dibheader.header_size != 40) || (dibheader.compression != 0) || (dibheader.bitsperpixel != 24)) 
        fclose(fp);
        return 1;
    

    fseek(fp, header.image_offset, SEEK_SET);
    struct Image image = readImage(fp, dibheader.height, dibheader.width);
    createImage(header, dibheader, image);

    fclose(fp);
    freeImage(image);

    return 0;


int main() 
    openbmpfile();

我还尝试通过将文件头大小减小文件大小来手动写入大小,但没有奏效。

提前致谢!

【问题讨论】:

确保对齐正确。试试#pragma pack(push, 1) struct ... #pragma pack(pop) 请不要为特定的 32 位或 16 位整数定义自己的类型别名。使用标准的uint16_tuint32_t(在&lt;stdint.h&gt; 中定义)。 @extratype 我试过了,没用 typedef unsigned int int32; 让一个已经很痛苦的话题更加混乱:错位的微软遗留问题。 【参考方案1】:

BITMAP_headerDIB_header 的格式要求结构大小为 14 和 40。但是编译器对齐结构,大小会有所不同。您必须使用编译器特定的标志来禁用结构对齐(不清楚您使用哪个编译器)。 RGB 结构也是同样的问题。

还有字节序依赖性,位图不以 RGB 格式存储像素,它会翻转字节。

typedef unsigned int int32;

这会将unsigned 隐藏为signed。结构中的一些值应该是有符号的。例如,高度可以是负数,表示它应该被翻转。

你可以一一读取整数,使用

void readint(FILE *f, int *val, int size)

    unsigned char buf[4];
    fread(buf, size, 1, f);
    *val = 0;
    for (int i = size - 1; i >= 0; i--)
        *val += (buf[i] << (8 * i));


void writeint(FILE* f, int val, int size)
   
    unsigned char buf[4];
    for (int i = size - 1; i >= 0; i--)
        buf[i] = (val >> 8 * i) & 0xff;
    fwrite(buf, size, 1, f);

然后你读取一个 24 位的位图如下:

fread(header.name, 2, 1, fp); //BM
readint(fp, &header.size, 4);
readint(fp, &header.garbage, 4);
readint(fp, &header.image_offset, 4);

readint(fp, &dibheader.header_size, 4);
readint(fp, &dibheader.width, 4);
readint(fp, &dibheader.height, 4);
readint(fp, &dibheader.colorplanes, 2);
readint(fp, &dibheader.bitsperpixel, 2);
readint(fp, &dibheader.compression, 4);
readint(fp, &dibheader.image_size, 4);
readint(fp, &dibheader.temp[0], 4);
readint(fp, &dibheader.temp[1], 4);
readint(fp, &dibheader.temp[2], 4);
readint(fp, &dibheader.temp[3], 4);

char *buf = malloc(dibheader.image_size);
if (!buf)
    return 0;
fread(buf, 1, dibheader.image_size, fp);

还有其他问题需要考虑,例如位图的宽度对齐等。您希望从宽度为 4 的倍数的 24 位位图开始。

要写入位图,使用二进制标志,按相同顺序写入

FILE* fout  = fopen("new.bmp", "wb");
if (fout == NULL) return 0;
fwrite(header.name, 1, 2, fout);
writeint(fout, header.size, 4);
writeint(fout, header.reserved, 4);
writeint(fout, header.image_offset, 4);
writeint(fout, info.header_size, 4);
writeint(fout, info.width, 4);
writeint(fout, info.height, 4);
writeint(fout, info.colorplanes, 2);
writeint(fout, info.bitsperpixel, 2);
writeint(fout, info.compression, 4);
writeint(fout, info.image_size, 4);
writeint(fout, info.temp[0], 4);
writeint(fout, info.temp[1], 4);
writeint(fout, info.temp[2], 4);
writeint(fout, info.temp[3], 4);
fwrite(buf, 1, info.image_size, fout);

【讨论】:

非常感谢!我进行了更改,它现在确实翻转了图片。但是仍然存在打印的image_size为0的问题。另外,我检查了BMP文件格式,它确实将像素保存为RGB 您的写入函数不使用二进制标志。按照更新答案中的示例进行操作。这主要是为了锻炼。已经有图书馆可以读取位图。标准格式以 BGR 格式写入。

以上是关于C 中的 .BMP 文件 - DIB 标头返回图像大小 0,即使 BMP 标头返回文件大小的主要内容,如果未能解决你的问题,请参考以下文章

bmp 和JPG有啥区别

BMP文件格式具体解释

BMP格式详解

BMP格式详解

BMP格式详解

BMP文件格式详解