Read and write BMP image (grayscale)

60 views Asked by At

I have this code to read bmp image and then write it in new image. The problem that I faced is that the resulting image from write_bmp function give the same size of the original reading image but I can not open the image because it corrupted. Can you help me to solve the issue in the code please? thank you in advance.

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

#pragma pack(push, 1)
typedef struct {
    uint16_t signature;
    uint32_t file_size;
    uint32_t reserved;
    uint32_t data_offset;
    uint32_t header_size;
    int32_t width;
    int32_t height;
    uint16_t planes;
    uint16_t bits_per_pixel;
    uint32_t compression;
    uint32_t image_size;
    int32_t x_pixels_per_meter;
    int32_t y_pixels_per_meter;
    uint32_t total_colors;
    uint32_t important_colors;
} BMPHeader;
#pragma pack(pop)


void read_bmp(const char* filename, uint8_t** image_data, int32_t* width, int32_t* height, uint16_t* bits_per_pixel)
{
    FILE* file = fopen(filename, "rb");
    if (!file) {
        fprintf(stderr, "Failed to open the file\n");
        exit(1);
    }

    BMPHeader header;
    fread(&header, sizeof(BMPHeader), 1, file);

    if (header.signature != 0x4D42) {
        fprintf(stderr, "Invalid BMP file\n");
        exit(1);
    }

    *width = header.width;
    *height = header.height;
    *bits_per_pixel = header.bits_per_pixel;

    int32_t row_padding = (4 - ((*width) % 4)) % 4;
    int32_t row_size = (*width) + row_padding;
    int32_t data_size = row_size * abs(*height);

    *image_data = (uint8_t*)malloc(data_size);
    if (!*image_data) {
        fprintf(stderr, "Memory allocation failed\n");
        exit(1);
    }

    fseek(file, header.data_offset, SEEK_SET);

    int32_t row;
    for (row = 0; row < abs(*height); ++row) {
        fread(*image_data + (row * row_size), 1, *width, file);
        fseek(file, row_padding, SEEK_CUR);
    }

    fclose(file);
}

void write_bmp(const char* filename, const uint8_t* image_data, int32_t width, int32_t height, uint16_t bits_per_pixel)
{
    FILE* file = fopen(filename, "wb");
    if (!file) {
        fprintf(stderr, "Failed to open the file 4 write\n");
        exit(1);
    }

    int32_t row_padding = (4 - ((width * (bits_per_pixel / 8)) % 4)) % 4;
    int32_t row_size = (width * (bits_per_pixel / 8)) + row_padding;
    int32_t data_size = row_size * abs(height);

    BMPHeader header;
    header.signature = 0x4D42;
    header.file_size = sizeof(BMPHeader) + data_size;
    header.reserved = 0;
    header.data_offset = sizeof(BMPHeader);
    header.header_size = sizeof(BMPHeader) - 14;
    header.width = width;
    header.height = height;
    header.planes = 1;
    header.bits_per_pixel = bits_per_pixel;
    header.compression = 0;
    header.image_size = data_size;
    header.x_pixels_per_meter = 0;
    header.y_pixels_per_meter = 0;
    header.total_colors = 0;
    header.important_colors = 0;

    fwrite(&header, sizeof(BMPHeader), 1, file);

    int32_t row;
    for (row = 0; row < abs(height); ++row) {
        fwrite(image_data + (row * width * (bits_per_pixel / 8)), 1, (width * (bits_per_pixel / 8)), file);
        uint8_t padding[3] = {0};
        fwrite(padding, 1, row_padding, file);
    }

    fclose(file);
}

int main()
{

    const char* input_filename = "C:/Users/pp/Desktop/r/lena.BMP";
    const char* output_filename = "C:/Users/pp/Desktop/r/ccoutput.BMP";


    uint8_t* image_data;
    int32_t width, height;
    uint16_t bits_per_pixel;

    read_bmp(input_filename, &image_data, &width, &height, &bits_per_pixel);
    printf("bits_per_pixel = %d \n",bits_per_pixel );

    // Access and process the image data as needed

    write_bmp(output_filename, image_data, width, height, bits_per_pixel);

    free(image_data);

    return 0;
}

1

There are 1 answers

0
Steampunkery On

Taking a look at your code, it looks like you forgot about the color palette data that comes between the end of the header and the beginning of the pixel data. For this answer, I am using this image as a test.

The difference in the size of the input file and output file is exactly 1026 bytes. Since the image has 8 bits per pixel and colors are stored in the 4-byte RGBA32 format, the table takes up 2^8*4=1024 bytes. Note that the color table is not optional for images with bits-per-pixel <= 8.

The remaining two bytes that account for the 1026-byte image size difference appear to be a 2-byte null terminator at the end of the file.

Additionally, in your read_bmp function, you don't take into account the bits-per-pixel in the way that you do in write_bmp.

Finally, you should heed Sven Nilsson's advice because your calculations for the header is also not totally correct.

Sources: https://en.wikipedia.org/wiki/BMP_file_format#Color_table