3

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.

Dusan Malic
  • 231
  • 4
  • 15
  • 1
    Looks like a struct member alignment issue here. What happens if you put `#pragma pack(1)` just before declaring the structs ? – SirDarius Nov 30 '14 at 16:55
  • Now I got "Error reading BMP file header from ..." – Dusan Malic Nov 30 '14 at 16:59
  • 1
    Make sure you are using the correct ordering of bytes for multi-byte values – pmg Nov 30 '14 at 17:01
  • The error you are quoting cannot possibly occur in the code you pasted in your question. How did you get it ? – SirDarius Nov 30 '14 at 17:03
  • I copy/pasted code from above, compiled and runed it, and tried to open image with GIMP. – Dusan Malic Nov 30 '14 at 17:08
  • you need to (using an appropriate editor) examine the contents of the output file. to assure it exactly matches the format listed at: which fully defines the BMP format and has this statement: " All of the integer values are stored in little-endian format (i.e. least-significant byte first)." – user3629249 Nov 30 '14 at 17:58
  • 2
    typedef unsigned int UINT is most likely a different size on 64bit compared to 32bit. – hookenz Nov 30 '14 at 18:54
  • this link (and the related links found at the bottom of the page) will tell you everything you ever wanted to know about BMP file formats/contents – user3629249 Nov 30 '14 at 19:33
  • this link: contains a very clear tutorial on how to load/save BMP images, using the windows API. (which assumes the underlying hardware is little endian, so you may have to tweak the method of setting some values so they are saved in the correct format (I.E. byte at a time) – user3629249 Nov 30 '14 at 20:15

3 Answers3

4

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.

Community
  • 1
  • 1
Vyktor
  • 20,559
  • 6
  • 64
  • 96
  • 1
    Just as a side note: bmp was a format used by 16-bit windows which is why UINT is uint16_t. On the 16 bit compilers, int was 16 bits. – cup Nov 30 '14 at 19:18
2

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)
Weather Vane
  • 33,872
  • 7
  • 36
  • 56
  • Its still same. I am using [this](http://www.digicamsoft.com/bmp/bmp.html) as my main ref and they say that it should be `unsigned int`. – Dusan Malic Nov 30 '14 at 18:18
  • It does not say `unsigned int` it says `UINT` And where on that page is `UINT` defined? I don't care what your page says, I compiled and ran your program with the alterations commented here and it creates a 600x600 black 24-bit bitmap that my image viewer displays. – Weather Vane Nov 30 '14 at 18:29
  • Did you include the `pack` pragmas that @SirDarius recommended in the very first comment (which I added to my answer after your recent comment)? – Weather Vane Nov 30 '14 at 18:37
  • Weird thing, but I just compiled it on my windows PC and it worked there. As I mentioned in post I'm using Ubuntu 14.04. – Dusan Malic Nov 30 '14 at 18:45
  • 1
    Defining a portable file format with machine-specific field sizes is a no-no, see comment from @Matt. – Weather Vane Nov 30 '14 at 19:01
  • Is there any way to go around this? – Dusan Malic Nov 30 '14 at 19:05
  • define all fields as char name[size] then fill the field byte by byte. then there is no problem with alignment bytes, etc – user3629249 Dec 01 '14 at 02:48
2

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);
}
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42