RFiischer
RFiischer

Reputation: 51

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

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?

Upvotes: 0

Views: 195

Answers (2)

too honest for this site
too honest for this site

Reputation: 12263

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.

Upvotes: 1

Hi-Angel
Hi-Angel

Reputation: 5619

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

Upvotes: 0

Related Questions