parallel_resistance
parallel_resistance

Reputation: 155

Bitmap writing program doesn't produce a readable image despite following format specifications

I'm making a bitmap writer in C as part of a larger project. I followed the windows .bmp header format specifications and checked the generated file in a hex editor to compare it with functional .bmp images, however all image programs I have on my computer cannot open it. Here is the code:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#pragma pack(push, 1) /* remove padding from sructs */

void generateBitmap(int width, int height, float dpi, const char* filename, pixel* imgData) {
    FILE* bitmap;

    struct fHeader {
        uint16_t type; 
        uint32_t size;
        uint16_t reserved1;
        uint16_t reserved2;
        uint32_t offset;
    }bmpFileHeader;

    struct iHeader {
        uint32_t headerSize;
        int32_t  width;
        int32_t  height;
        uint16_t planes;
        uint16_t bitCount;
        uint32_t compression;
        uint32_t imageSize; /* may be 0 if uncompressed */
        int32_t  xPPM;
        int32_t  yPPM;
        uint32_t colorEntriesUsed;
        uint32_t importantColors;
    }bmpImageHeader;

    int bytesPerPixel = 3; /* 24 bit color */
    uint32_t imgSize = width * height;
    uint32_t fileSize = sizeof(bmpFileHeader) + sizeof(bmpImageHeader) + (bytesPerPixel * width * height);
    int32_t ppm = dpi * 39;

    bmpFileHeader.type = 0x4D42;
    bmpFileHeader.size = fileSize;
    bmpFileHeader.reserved1 = 0;
    bmpFileHeader.reserved2 = 0;
    bmpFileHeader.offset = sizeof(bmpFileHeader) + sizeof(bmpImageHeader);

    bmpImageHeader.headerSize = sizeof(bmpImageHeader);
    bmpImageHeader.width = width;
    bmpImageHeader.height = height;
    bmpImageHeader.planes = 1;
    bmpImageHeader.bitCount = 8 * bytesPerPixel;
    bmpImageHeader.compression = 0;
    bmpImageHeader.imageSize = bytesPerPixel * height * width;
    bmpImageHeader.xPPM = ppm; /* typically set these to zero */
    bmpImageHeader.yPPM = ppm;
    bmpImageHeader.colorEntriesUsed = 0;
    bmpImageHeader.importantColors = 0;

    bitmap = fopen(filename, "wb");
    fwrite(&bmpFileHeader, 1, sizeof(bmpFileHeader), bitmap);
    fwrite(&bmpImageHeader, 1, sizeof(bmpImageHeader), bitmap);

    int i;
    for (i = 0; i < (width * height); i++) {
        fwrite(&imgData[i], 3, sizeof(char), bitmap);
    }

    fclose(bitmap);
}

int main(void) {
    pixel imData[4];

    int i;

    for(i = 0; i < 4; i++) {
        imData[i].r = 32;
        imData[i].g = 64;
        imData[i].b = 32;
    }

    generateBitmap(2, 2, 0, "bmptest.bmp", imData);

    return 0;
}

The end result should just be a monotone 2x2 image. Some examples I've found set some more header values, such as imageSize to zero, but other examples treated them as I have here.

Upvotes: 0

Views: 55

Answers (2)

Barmak Shemirani
Barmak Shemirani

Reputation: 31599

In bitmap format, pixels are padded so that "width in bytes" for each line, is a multiple of 4 bytes. See link for more details.

In this case you have 2 pixels per line, each pixel is 3 bytes. So you have 6 bytes per line. It has to be padded so each line is 8 bytes (the last 2 bytes are ignored).

This also affects the size of the bitmap file, and imageSize of iHeader structure.

int bytesPerPixel = 3; 
int bits_per_pixel = bytesPerPixel * 8;
int width_in_bytes = ((width * bits_per_pixel + 31) / 32) * 4;

...
bmpFileHeader.size = sizeof(bmpFileHeader)+sizeof(bmpImageHeader) + width_in_bytes*height;

...
bmpImageHeader.imageSize = width_in_bytes*height; 

You want to rewrite the loop so it goes from top to bottom, left to right

for(int y = height - 1; y >= 0; y--)
{
    for(int x = 0; x < width; x++)
    {
        int i = y * width + x;
        fwrite(&imgData[i], 3, sizeof(char), bitmap);
    }

    for(int x = width * bits_per_pixel / 8; x < width_in_bytes; x++)
        fputc('\0', bitmap);
}

.xPPM and .yPPM can be zero. Lastly you can use #pragma pack(pop) to restore compiler's default pack

#pragma pack(push, 1) /* remove padding from sructs */
    struct fHeader {
    ...
    };

    struct iHeader {
    ...
    };
#pragma pack(pop)

Upvotes: 1

parallel_resistance
parallel_resistance

Reputation: 155

Okay, I finally got it to work. The problem was with the offset padding bits between rows of pixels. I personally didn't find it to be incredibly well documented, but the .bmp file format expects null padding bytes at the end of each row depending on the dimensions of the image. All I needed to do was add this line in to the pixel writing code:

int i;
int p;
for (i = 0; i < (width * height); i++) {
    fwrite(&imgData[i], 3, sizeof(char), bitmap);
    if ((i + 1) % width == 0) { /* if at end of scanline */
        for(p = 0; p < (width % 4); p++) {
            fputc('\0', bitmap); /* append appropriate # of padding bits */
        }
    }
}

Upvotes: 1

Related Questions