1

I would like to generate a BMP file from RGB values that I have stored already.

I'm programming on OS X so I can't use the predefined BMP headers.

I've tried doing the below but preview says that the file is corrupted.

void bitmap(Image * image) {

typedef struct                       /**** BMP file header structure ****/
{
    unsigned short bfType;           /* Magic number for file */
    unsigned int   bfSize;           /* Size of file */
    unsigned short bfReserved1;      /* Reserved */
    unsigned short bfReserved2;      /* ... */
    unsigned int   bfOffBits;        /* Offset to bitmap data */
} BITMAPFILEHEADER;

typedef struct                       /**** BMP file info structure ****/
{
    unsigned int   biSize;           /* Size of info header */
    int            biWidth;          /* Width of image */
    int            biHeight;         /* Height of image */
    unsigned short biPlanes;         /* Number of color planes */
    unsigned short biBitCount;       /* Number of bits per pixel */
    unsigned int   biCompression;    /* Type of compression to use */
    unsigned int   biSizeImage;      /* Size of image data */
    int            biXPelsPerMeter;  /* X pixels per meter */
    int            biYPelsPerMeter;  /* Y pixels per meter */
    unsigned int   biClrUsed;        /* Number of colors used */
    unsigned int   biClrImportant;   /* Number of important colors */
} BITMAPINFOHEADER;

BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;

bfh.bfType = 0x4d42;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfOffBits = 0x36;

bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = image->getWidth();
bih.biHeight = image->getHeight();
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 0x0ec4;
bih.biYPelsPerMeter = 0x0ec4;
bih.biClrUsed = 0;
bih.biClrImportant = 0;

FILE *file = fopen("a.bmp", "wb");
if (!file) {
    cout << "File not found";
    return;
}

fwrite(&bfh, 1, sizeof(bfh), file);
fwrite(&bih, 1, sizeof(bfh), file);

for (int x = 0; x < image->getWidth(); x++) {
    for (int y = 0; y < image->getHeight(); y++) {
        float r = image->getPixel(x, y).r;
        float g = image->getPixel(x, y).g;
        float b = image->getPixel(x, y).b;
        fwrite(&r, 1, 1, file);
        fwrite(&g, 1, 1, file);
        fwrite(&b, 1, 1, file);
    }
}
}

I'm not sure that I understand the structure correctly. I've tried reading about it but I must be missing something.

Here is the hex output of the file

42 4D 00 02 38 00 00 00 00 00 00 00 36 00 00 00 28 00 00 00 80 02 00 00 E0 01 00 00 01 00 18 00 00 00 00 00 00 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

mcarlie
  • 13
  • 1
  • 5
  • 1
    For a start you're trying to write floating-point values to the bitmap. You want to use 8-bit integers for the color channels. http://stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-c-c-without-other-libraries –  May 24 '15 at 13:00
  • Since you are writing in C++, you should by convention avoid the `typedef struct{} BLAH;` thing. Prefer struct Blah{}; . It does work anyway, but typedefs are bad things here since the prohibit predeclaration. – user877329 May 24 '15 at 13:53

2 Answers2

8

The question is good because it enlightens some pitfalls. Here are all issues in your code. By fixing them, everything works as expected:

  • Your bfSize field does not include the size of the bitmap

  • General: The BITMAPFILEHEADER becomes to large, since it begins with a 2 byte value followed by a 4 byte value. Padding rules say that this struct the first field will be 4 bytes instead of 2. Solution: Write the magic number separately by excluding it from BITMAPFILEHEADER:

    unsigned short magic=0x4d42; //This field is _not_ included in `BITMAPFILEHEADER`
    fwrite(&magic,1,sizeof(magic),file);
    
    // Write the remaining part of the header (if you did not get an I/O error...)
    

    This modification also implies that BITMAPFILEHEADER::bfSize is 2+sizeof(BITMAPFILEHADER) + sizeof(BITMAPINFOHEADER)+ biWidth*biHeight*3

  • You have also passed sizeof(bfh) for booth bfhand bih, so the complete BITMAPINFOHEADER is never written

  • The BMP file format requires that each scanline is DWORD-aligned so depending on the width, you may need to write zero bytes after each scanline

  • If you think the first scanline is up, your image is upside down, since your height is positive.

  • Bitmap files are stored row-wise, so you should loop over x-coordinates in the innermost loop

  • You have also write the wrong pixel values. You should write unsigned char instead of float. You write the correct size (1 byte), but it will not be the correct value:

    unsigned char r = image->getPixel(x, y).r/255; //If 1.0f is white.
    

    Also, bitmaps are BGR(A) and not RGB(A).

Assuming you have a nice width, a working example is

void bitmap()
{
typedef struct                       /**** BMP file header structure ****/
    {
    unsigned int   bfSize;           /* Size of file */
    unsigned short bfReserved1;      /* Reserved */
    unsigned short bfReserved2;      /* ... */
    unsigned int   bfOffBits;        /* Offset to bitmap data */
    } BITMAPFILEHEADER;

typedef struct                       /**** BMP file info structure ****/
    {
    unsigned int   biSize;           /* Size of info header */
    int            biWidth;          /* Width of image */
    int            biHeight;         /* Height of image */
    unsigned short biPlanes;         /* Number of color planes */
    unsigned short biBitCount;       /* Number of bits per pixel */
    unsigned int   biCompression;    /* Type of compression to use */
    unsigned int   biSizeImage;      /* Size of image data */
    int            biXPelsPerMeter;  /* X pixels per meter */
    int            biYPelsPerMeter;  /* Y pixels per meter */
    unsigned int   biClrUsed;        /* Number of colors used */
    unsigned int   biClrImportant;   /* Number of important colors */
    } BITMAPINFOHEADER;

BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;

/* Magic number for file. It does not fit in the header structure due to alignment requirements, so put it outside */
unsigned short bfType=0x4d42;           
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfSize = 2+sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+640*480*3;
bfh.bfOffBits = 0x36;

bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = 640;
bih.biHeight = 480;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 5000;
bih.biYPelsPerMeter = 5000;
bih.biClrUsed = 0;
bih.biClrImportant = 0;

FILE *file = fopen("a.bmp", "wb");
if (!file)
    {
    printf("Could not write file\n");
    return;
    }

/*Write headers*/
fwrite(&bfType,1,sizeof(bfType),file);
fwrite(&bfh, 1, sizeof(bfh), file);
fwrite(&bih, 1, sizeof(bih), file);

/*Write bitmap*/
for (int y = bih.biHeight-1; y>=0; y--) /*Scanline loop backwards*/
    {
    for (int x = 0; x < bih.biWidth; x++) /*Column loop forwards*/
        {
        /*compute some pixel values*/
        unsigned char r = 255*((float)x/bih.biWidth);
        unsigned char g = 255*((float)y/bih.biHeight);
        unsigned char b = 0;
        fwrite(&b, 1, 1, file);
        fwrite(&g, 1, 1, file);
        fwrite(&r, 1, 1, file);
        }
    }
fclose(file);
}

This is the output

Jongware
  • 22,200
  • 8
  • 54
  • 100
user877329
  • 6,717
  • 8
  • 46
  • 88
  • I'm not sure what you mean by the first one. How do I write the magic number separately? If by DWORD aligned you mean that the width needs to be divisible by 4 then it I shouldn't need to pad if the width is 640 right? – mcarlie May 24 '15 at 13:02
  • If you have 640 px you are fine, since 640/4=160 – user877329 May 24 '15 at 13:06
  • From what I've read the number should be included in the header structure. In any case I tried writing it separately but it still claims that it's corrupted. – mcarlie May 24 '15 at 13:24
  • Also if I set the height to negative it claims that the file is empty – mcarlie May 24 '15 at 13:27
  • It may happen that your image viewer does not support inverted bitmaps. – user877329 May 24 '15 at 13:49
  • I tried it with windows and it didn't work there either – mcarlie May 24 '15 at 13:53
  • 1
    The negative height can be avoided by looping from bottom to top instead of bottom to top. To find what's wrong, it is a good idea to open your output in a hex editor and compare it with an existing bitmap of the same pixel size and spatial resolution. – user877329 May 24 '15 at 13:56
-1

user877329's Code worked for me. (Just some minor changes because im using C) Note: Just pay attention, to the 'size' of the Variables, because it can be different, depending on which platform you Compile.

Aime
  • 21
  • 3