Create BMP header in C (can't limit 2 byte fields)

199 views Asked by At

I'm doing it based on:

https://en.wikipedia.org/wiki/BMP_file_format

I want to create a BMP image from scratch in C.

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

typedef struct HEADER {
    short FileType;
    int FileSize;
    short R1;
    short R2;
    int dOffset;
} tp_header;

int main () {
    FILE *image;

    image = fopen("test.bmp", "w");

    tp_header bHeader;

    bHeader.FileType = 0x4D42;
    bHeader.FileSize = 70;
    bHeader.R1 = 0;
    bHeader.R2 = 0;
    bHeader.dOffset = 54;

    fwrite(&bHeader, sizeof(struct HEADER), 1, image);

    return 0;
}

I should be getting at output file:

42 4D 46 00 00 00 00 00 00 00 36 00 00 00

But instead i get:

42 4D 40 00 46 00 00 00 00 00 00 00 36 00 00 00

First of it should contain only 14 bytes. That "40 00" ruins it all. Is that the propper way of setting the header in C? How else can i limit the size in bytes outputed?

2

There are 2 answers

0
Hi-Angel On

The problem is that your struct is aligned. You ought to write it like

#pragma pack(push, 1)
typedef struct HEADER {
    short FileType;
    int FileSize;
    short R1;
    short R2;
    int dOffset;
} tp_header;
#pragma pack(pop)

Just for you to know — the compiler for optimizing reasons by default would lay it out like:

typedef struct HEADER {
    short FileType;
    char empty1; //inserted by compiler
    char empty2; //inserted by compiler
    int FileSize;
    short R1;
    short R2;
    int dOffset;
} tp_header;

But you actually made also another error: sizeof(int) ≥ 4 bytes. I.e. depending on a platform integer could be 8 bytes. It is important, in such a cases you have to use types like int32_t from cstdint

4
too honest for this site On

A struct might include padding bytes between the fields to align the next field to certain address offsets. The values of these padding bytes are indetermined. A typical layout might look like:

struct {
    uint8_t field1;
    uint8_t <padding>
    uint8_t <padding>
    uint8_t <padding>
    uint32_t field2;
    uint16_t field3;
    uint8_t <padding>
    uint8_t <padding>
};

<padding> is just added by the compile; it is not accessible by your program. This is just an example. Actual padding may differ and is defined by the ABI for your architecture (CPU/OS/toolchain).

Also, the order in which the bytes of a larger type are stored in memory (endianess) depends on the architecture. However, as the file requires a specific endianess, this might also have to be fixed.

Some - but not all - compilers alow to specify a struct to be packed (avoid padding), that still does not help with the endianess-problem.

Best is to serialize the struct properly by shifts and store to an uint8_t-array:

#include <stdint.h>

/** Write an uint16_t to a buffer.
 *
 *  \returns The next position in the buffer for chaining.
 */
inline uint8_t *writeUInt16(uint8_t *bp, value)
{
    *bp++ = (uint8_t)value;
    *bp++ = (uint8_t)(value >> 8);
    return bp;
}

// similar to writeUInt16(), but for uint32_t.
... writeUInt32( ... )
    ...

int main(void)
{
    ...

    uint8_t buffer[BUFFER_SIZE], *bptr;

    bptr = buffer;
    bptr = writeUInt16(bptr, 0x4D42U);    // FileType
    bptr = writeUInt32(bptr, 70U);        // FileSize
    ...

}

That will fill buffer with the header fields. BUFFER_SIZE has to be set according to the header you want to create. Once all fields are stored, write buffer to the file.

Declaring the functions inline hints a good compiler to create almost optimal code for constants.

Note also, that the sizes of short, etc. are not fixed. Use stdint.h types is you need types of defined size.