C 中的 .BMP 文件 - DIB header returns 图像大小 0 即使 BMP header returns 文件大小

.BMP files in C - DIB header returns image size 0 even though the BMP header returns the file size

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

这是我的代码:

#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();
}

我也尝试通过将 header 大小减小文件大小来手动写入大小,但它没有用。

提前致谢!

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