Search code examples
c++bmp

Generate BMP file from array of RGB values


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


Solution

  • 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