Dusan Malic
Dusan Malic

Reputation: 231

Creating bmp file in C

I am trying to create .bmp file (filled with one colour for testing purposes).

Here is code that I'm using:

#include <stdio.h>

#define BI_RGB 0

typedef unsigned int UINT;
typedef unsigned long DWORD;
typedef long int LONG;
typedef unsigned short WORD;
typedef unsigned char BYTE;

typedef struct tagBITMAPFILEHEADER {
    UINT bfType;
    DWORD bfSize;
    UINT bfReserved1;
    UINT bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
} BITMAPINFOHEADER;

typedef struct COLORREF_RGB
{
    BYTE cRed;
    BYTE cGreen;
    BYTE cBlue;
}COLORREF_RGB;

int main(int argc, char const *argv[])
{

    BITMAPINFOHEADER bih;

    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biWidth = 600;
    bih.biHeight = 600;
    bih.biSizeImage = bih.biWidth * bih.biHeight * 3;
    bih.biPlanes = 1;
    bih.biBitCount = 24;
    bih.biCompression = BI_RGB;
    bih.biXPelsPerMeter = 2835;
    bih.biYPelsPerMeter = 2835;
    bih.biClrUsed = 0;
    bih.biClrImportant = 0;



    COLORREF_RGB rgb;
    rgb.cRed = 0;
    rgb.cGreen = 0;
    rgb.cBlue = 0;



    BITMAPFILEHEADER bfh;

    bfh.bfType = 0x424D;
    bfh.bfReserved1 = 0;
    bfh.bfReserved2 = 0;
    bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + bih.biSize;
    bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
        bih.biWidth * bih.biHeight * 4;


    FILE *f;
    f = fopen("test.bmp","wb");
    fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, f);
    fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, f);

    int i,j;
    for(i = 0; i < bih.biHeight; i++)
    {
        for(j = 0; j < bih.biWidth; j++)
        {
            fwrite(&rgb,sizeof(COLORREF_RGB),1,f);
        }
    }

    fclose(f);

    return 0;
}

and jet every time I compile and run it I get error saying that its not valid BMP image. I double checked all values in multiple references and still can't find error here.

Did I misunderstood something and what am I doing wrong here?

Also not sure if important but I am using Ubuntu 14.04 to compile this.

EDIT

Found one more issue :

bfh.bfType = 0x424D;

should be

bfh.bfType = 0x4D42;

But still can't see image.

Upvotes: 3

Views: 2709

Answers (3)

Nisse Engstr&#246;m
Nisse Engstr&#246;m

Reputation: 4761

You are trying to map a C structure to some externally-defined binary format There are a number of problems with this:

Size

typedef unsigned int UINT;
typedef unsigned long DWORD;
typedef long int LONG;
typedef unsigned short WORD;
typedef unsigned char BYTE;

The C language only specify the minimum sizes of these types. Their actual sizes can and do vary between different compilers and operating systems. The sizes and offset of the members in your structures may not be what you expect. The only size that you can rely on (on almost any system that you are likely to encounter these days) is char being 8 bits.

Padding

typedef struct tagBITMAPFILEHEADER {
    UINT bfType;
    DWORD bfSize;
    UINT bfReserved1;
    UINT bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

It is common for DWORD to be twice as large as a UINT, and in fact your program depends on it. This usually means that the compiler will introduce padding between bfType and bfSize to give the latter appropriate alignment for its type. The .bmp format has no such padding.

Order

BITMAPFILEHEADER bfh;

bfh.bfType = 0x424D;
...
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, f);

Another problem is that the C language does not specify the endianess of the types. The .bfType member may be stored as [42][4D] (Big-Endian) or [4D][42] (Little-Endian) in memory. The .bmp format specifically requires the values to be stored in Little-Endian order.

Solution

You might be able to solve some of these problems by using compiler-specific extensions (such as #pragma's or compiler switches), but probably not all of them.

The only way to properly write an externally-defined binary format, is to use an array of unsigned char and write the values a byte at a time. Personally, I would write a set of helper functions for specific types:

void w32BE (unsigned char *p, unsigned long ul)
{
  p[0] = (ul >> 24) & 0xff;
  p[1] = (ul >> 16) & 0xff;
  p[2] = (ul >>  8) & 0xff;
  p[3] = (ul      ) & 0xff;
}
void w32LE (unsigned char *p, unsigned long ul)
{
  p[0] = (ul      ) & 0xff;
  p[1] = (ul >>  8) & 0xff;
  p[2] = (ul >> 16) & 0xff;
  p[3] = (ul >> 24) & 0xff;
}
/* And so on */

and some functions for writing the entire .bmp file or sections of it:

int function write_header (FILE *f, BITMAPFILEHEADER bfh)
{
  unsigned char buf[14];

  w16LE (buf   , bfh.bfType);
  w32LE (buf+ 2, bfh.bfSize);
  w16LE (buf+ 6, bfh.bfReserved1);
  w16LE (buf+ 8, bfh.bfReserved2);
  w32LE (buf+10, bfh.bfOffBits);

  return fwrite (buf, sizeof buf, 1, f);
}

Upvotes: 2

Vyktor
Vyktor

Reputation: 21007

First of all, you set:

bfSize Specifies the size of the file, in bytes.

to invalid value, your code resulted into 1440112 while size of file is actually 1080112

bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
    bih.biWidth * bih.biHeight * sizeof(COLORREF_RGB);    

Because sizeof(COLORREF_RGB) is actually 4 not 3.

Another mistake is that size of your structs and types:

                                  expected size        actual size*
typedef unsigned int UINT;    //              2                  4
typedef unsigned long DWORD;  //              4                  8
typedef long int LONG;        //              4                  8
typedef unsigned short WORD;  //              2                  2
typedef unsigned char BYTE;   //              1                  1

* I'm using gcc on x86_64 architecture

Your offsets just don't match with offsets on wikipedia, reference you are using was probably written for 16 bit compiler on 16 bit OS (as pointed out by cup in a comment) so it assumes int to be 2B type.

Using values from stdint.h worked for me (guide on stdint.h for Visual Studio here):

#include <stdint.h>

typedef uint16_t UINT;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint16_t WORD;
typedef uint8_t BYTE;

And last but not least you have to turn off memory alignment as suggested by Weather Vane.

Upvotes: 4

Weather Vane
Weather Vane

Reputation: 34583

I believe your field sizes to be incorrect, try this

#pragma pack(push, 1)

typedef struct tagBITMAPFILEHEADER {
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

#pragma pack(pop)

Upvotes: 2

Related Questions