Question

I have a project that involves reading a .bmp file into a C program, putting a mask on it, and printing the version with the mask back to a different file. The part I'm having an issue with seems to be the actual process of reading in the file. The first big red flag I'm seeing is that it keeps reading in the wrong resolution. I've searched quite a bit and seen a few scripts to read in a .bmp file as answers to various questions here but using the logic from those scripts hasn't helped.

The primary issue seems to be that rather than reading in the proper dimensions of 200 x 300 on the example image given by my professor, it reads in 13107200 x 65536. However, if I were to include the part of the code that prints to a different file, you would see that the output file has the appropriate resolution. This tells me that I am likely reading in the information properly but not storing it in the way that I think I am.

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

struct HEADER {
    unsigned short int Type;        // Magic indentifier
    unsigned int Size;              // File size in bytes
    unsigned short int Reserved1, Reserved2;
    unsigned int Offset;            // Offset to data (in B)
} Header;                           // -- 14 Bytes

struct INFOHEADER {
    unsigned int Size;              // Header size in bytes
    int Width, Height;              // Width / height of image
    unsigned short int Planes;      // Number of colour planes
    unsigned short int Bits;        // Bits per pixel
    unsigned int Compression;       // Compression type
    unsigned int ImageSize;         // Image size in bytes
    int xResolution, yResolution;   // Pixels per meter
    unsigned int Colors;            // Number of colors
    unsigned int ImportantColors;   // Important colors
} InfoHeader;                       // -- 40 Bytes

struct PIXEL {
    unsigned char Red, Green, Blue; // Intensity of Red, Green, and Blue
};                                  // -- 3 Bytes

int getFileSize(FILE *input);
int getHexVal(FILE *input);
struct HEADER *getHeader(FILE *input);
struct INFOHEADER *getInfoHeader(FILE *input);
struct PIXEL *getPixel(FILE *input, struct PIXEL *loc);
struct HEADER *printHeader(FILE *output);
struct INFOHEADER *printInfoHeader(FILE *output);
struct PIXEL *printPixel(FILE *output, struct PIXEL *loc);

int main(int argc, char const *argv[]) {
    if (argc == 3) {
         if (!strcmp(argv[1], argv[2])) {
            printf("The input and output file must be different. Please try again.\n");
            return 1;
        }

        // char Matrix[3][3] =
        // { {  0, -1,  0 },
        //   { -1,  4, -1 },
        //   {  0, -1,  0 }
        // };

        FILE *input = fopen(argv[1], "rb");
        if (!input) return 1;
        int i, j;
        // getHeader(input);
        fread(&Header, sizeof(struct HEADER), 1, input);
        if (Header.Type != 0x4D42) {
            printf("The specified input file was not a bitmap. Please try again.");
            fclose(input);
            return 1;
        }
        // getInfoHeader(input);
        fread(&InfoHeader, sizeof(struct INFOHEADER), 1, input);
        fseek(input, Header.Offset, SEEK_SET);
        struct PIXEL arr[InfoHeader.Width][InfoHeader.Height];

        printf("%d %d\n", InfoHeader.Width, InfoHeader.Height);
        for (i = 0; i < InfoHeader.Width; i++) {
            for (j = 0; j < InfoHeader.Height; j++) {
                getPixel(input, arr[i] + j);
                printf("%d %d %d\n", arr[i][j].Red, arr[i][j].Green, arr[i][j].Blue);
            }
        }
        fclose(input);
    }
}
Was it helpful?

Solution

I can see a lot of problems with your code:

1. Inconsistent sizes of data types

On different platforms, types like int and short can have different sizes. So, int might be one size on one platform, and another size on a different platform. You may need to use exact sized types like uint32_t.

2. Padding and alignment

The headers stored in the files are packed. Your structs are aligned. That means that the compiler inserts padding between members to ensure that members are always aligned for optimal memory access.

There are a variety of ways of dealing with this. You could declare your structs to be packed. That would get you so far, but see the next point.

3. Endianness

If you are reading a Windows bitmap on a big endian system, you have convert from little endian data in the file to big endian data for your system.

4. xResolution, yResolution are the wrong members

These are meant to indicate a physical size of the pixels. In practice they are seldom specified. You meant to read Width and Height.

5. The VLA (gah!)

You are using a variable length array: struct PIXEL arr[InfoHeader.xResolution][InfoHeader.yResolution]. That is liable to lead to stack overflows for large bitmaps. You really need to use dynamically allocated memory for the pixel array.


How would I deal with these issues?

  • Use exact sized types.
  • Declare packed structs.
  • Read the structs from file, and then perform endian correction if needed.
  • Allocate the pixel array with malloc.

OTHER TIPS

The types int and short and so on are only guaranteed to have certain minimum sizes. They can vary on different implementations. Even if we assume that an int and short is four and two octets respectively, you will still run into problems when reading and writing your structures.

For example:

struct HEADER {
  unsigned short int Type;
  unsigned int Size;
  unsigned short int Reserved1, Reserved2;
  unsigned int Offset;
} Header;

In order to make Size suitably aligned for the processor, the compiler will (typically) insert padding between Type and Size which places Size at offset +4 instead of +2 (assuming the sizes mentioned above).

The best way to read (and write) binary formats is to read the file into an unsigned char * buffer, and then extract the fields from there. Eg.

unsigned long Size = buffer[2] +
                     buffer[3] * 0x100UL +
                     buffer[4] * 0x10000UL +
                     buffer[5] * 0x1000000UL;

or similar.

I suspect you mixed up some of the fields. After looking at http://en.wikipedia.org/wiki/BMP_file_format , I think instead of this

 struct PIXEL arr[InfoHeader.xResolution][InfoHeader.yResolution];

You really meant this:

 struct PIXEL arr[InfoHeader.Width][InfoHeader.Height];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top