Search code examples
cimagestructbmp

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


I'm trying to flip a BMP image in C. I ran into a problem where when I try to read the BMP header everything is alright and the values are correct, but when I try to read the DIB header I get all of the values right except for the image size (raw bitmap data). I get a zero, which is really weird considering I got the file size in the BMP header. I tried debugging the code and looking up the problem online but that didn't help much.

This is my code:

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

I also tried writing the size manually by decreasing the header size by the file size but it didn't work.

thanks in advance!


Solution

  • The format expect the structure size to be 14 and 40 for BITMAP_header and DIB_header. But the compiler aligns the structure and the size will be different. You have to use compiler specific flags to disable structure alignment (it's unclear which compiler you use). It's the same problem with RGB structure.

    There is also endian-ness dependence, and the fact that bitmap doesn't store pixels in RGB format, it flips the bytes.

    typedef unsigned int int32;
    

    This will hide unsigned as signed. Some of the values in the structure are supposed to be signed. For example, height could be negative, indicating that it's supposed to be flipped.

    You can read the integers one by one, using

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

    Then you read a 24-bit bitmap as follows:

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

    There are other issues you have to consider, for example width alignment of bitmap, etc. You want to start with a 24-bit bitmap whose width is multiple of 4.

    To write the bitmap, use the binary flag, write in the same order

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