Jump to content
  • Advertisement
Sign in to follow this  
Bj

Problems writing a BMP exporter

This topic is 1093 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am having some troubles when generating a .bmp file, from the code below i expected it to come out in shades of red, but as you'll see from the included picture it becomes both blue and green instead. Any ideas why this is happening?

struct Pixel
{
   unsigned char r; 
   unsigned char g;
   unsigned char b;
};

The main function generates the vector that holds all the pixel data, the color depends on the height of the image.

int main()
{
   std::vector<Pixel> ImageData;
   unsigned int Width = 640;
   unsigned int Height = 480;
 
   for (unsigned int i = 0; i < Height; i++)
   {
      for (unsigned int j = 0; j < Width; j++)
      {
         Pixel temp;
         temp.r = i % 255;
         temp.g = 0;
         temp.b = 0;
         ImageData.push_back(temp);
      }
   }
 
   WriteBitMap("raytracer.bmp", Width, Height, ImageData);
 
   return 0;
}
 
Writes the 2 headers needed for BMP files and then the image data.
bool WriteBitMap(std::string Filename, const unsigned int Width, const unsigned int Height, std::vector<Pixel> ImageData)
{
   //Create fileformat headers
 
   BitmapFileHeader fh;
   BitmapInfoHeader ih;
 
   fh.bmtype[0] = 'B';
   fh.bmtype[1] = 'M';
   fh.iFileSize = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + (Width*Height * 3);
   fh.iOffsetBits = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader);
 
   ih.iSizeHeader = 40;
   ih.iWidth = Width;
   ih.iHeight = Height;
   ih.iPlanes = 1;
   ih.iBitCount = 24;
   ih.Compression = 0;
 
   //Open file for writing
 
   std::ofstream file;
   file.open(Filename, std::ios::out);
 
   file.write(reinterpret_cast<char*>(&fh), sizeof(BitmapFileHeader));
   file.write(reinterpret_cast<char*>(&ih), sizeof(BitmapInfoHeader));
 
   for (auto it = ImageData.begin(); it != ImageData.end(); ++it)
   {
      char red = it->r;
      char green = 0;
      char blue = 0;
      file.write(&blue, sizeof(char));
      file.write(&green, sizeof(char));
      file.write(&red, sizeof(char));
   }
 
   file.close();
   return false;
}

 

Edit:

See solution further down smile.png

 

File needed to be opened in binary mode.

Edited by frob
Mod note, please don't mark as solved.

Share this post


Link to post
Share on other sites
Advertisement

To add on on the upside down:

 

If you store height as negative the bitmap data is top down instead.

 

Also, for iFileSize you must use the size calculated with scanline size as Gl2eenDl2agon described.

Share this post


Link to post
Share on other sites

Thank you for your answers. =)

 

 

First:

 

Bitmaps are stored BGR and not RGB.

 

I do store them that way:

for (auto it = ImageData.begin(); it != ImageData.end(); ++it)
{
   char red = it->r;
   char green = 0;
   char blue = 0;
   file.write(&blue, sizeof(char));
   file.write(&green, sizeof(char));
   file.write(&red, sizeof(char));
}

 

Second:

 

Bitmaps are 4 byte aligned. This means that each horizontal line comes out to a multiple of 4 bytes.

Typically this is called "scanline size". This means that ( y * w + x ) *3 doesn't work.

You will have to find the scanline size by finding the smallest multiple of 4 larger than the width in bytes.

Like for( int scansize = 0; scansize < w_bytes; scansize += 4); will work (but not the best way).

You will need to allocate a buffer of (height * scanline_size) bytes and find the offset for each pixel by ( y * scaline_size + x * bytes_per_pixel).

In other words a generic 2d array wont work unless you can be sure that the scanline size is the same as (width * bytes per pixel).

 

 

I thought using a width of 640 would be aligned since:

640 pixels * 3 chars = 1920bytes
 
1920 bytes % 4 = 0

Isnt this correct? ohmy.png

 

Edit: If I change to constant values it works, and generates a red picture as expected 

 

   temp.r = i % 255;

to

   temp.r = 255;
Edited by bjornp

Share this post


Link to post
Share on other sites

A small thing:

//temp.r = i % 255;
temp.r = i % 256;

Sorry I don't have the time to have a good look at your code, but I wrote a "gdi memory bitmap" class a while ago, and it has a method which saves it to disk. You can find the code in this blog post, and below is the relevant method. I'm sorry for just posting code without useful answers, but I hope it helps.


/////////////////////////////////////////////////////////////////
// Desc: Save bitmap represented by the specified BITMAPINFO struct to a file.
BOOL CMemoryBitmap::SaveBMPToFile( const PBITMAPINFO pbmi, LPCTSTR sFileName, VOID* pPixels )
{
    FILE* pFile = _tfopen( sFileName, TEXT("wb") );
    if( pFile == NULL )
        return FALSE;

    INT nWidth = pbmi->bmiHeader.biWidth;
    INT nHeight = pbmi->bmiHeader.biHeight;
    if( nHeight < 0 )
        nHeight = -nHeight;

    INT rowSizeUnaligned = pbmi->bmiHeader.biBitCount/8 * pbmi->bmiHeader.biWidth;
    INT rowSize = DWordAlign( rowSizeUnaligned );

    BITMAPFILEHEADER bmfh;
    bmfh.bfType = 0x4d42;    // 0x42 = "B" 0x4d = "M", says MSDN.

    // compute size of bitmap file.
    bmfh.bfSize =    sizeof( BITMAPFILEHEADER ) +
                    sizeof( BITMAPINFO ) +
                    rowSize;
    bmfh.bfReserved1 = 0;
    bmfh.bfReserved2 = 0;
    bmfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFO );

    if( 0 == fwrite( (VOID*)&bmfh, sizeof( BITMAPFILEHEADER ), 1, pFile ) )
    {
        //g_Log( "Failed to write file info header!" );
        fclose( pFile );
        return FALSE;
    }


    // NOTE: Some programs have problems loading bitmaps whose height is negative.
    // Therefore, this function will save the height as positive, causing the output
    // file to be vertically flipped.
    LONG oldHeight = pbmi->bmiHeader.biHeight;
    if( oldHeight < 0 )
        pbmi->bmiHeader.biHeight = -oldHeight;

    if( 0 == fwrite( (VOID*)pbmi, sizeof( BITMAPINFO ), 1, pFile ) )

    pbmi->bmiHeader.biHeight = oldHeight;



    // Now write the pixels:
    INT paddingBytes = rowSize - rowSizeUnaligned;
    DWORD dwZero = 0;

    BYTE* pBytes = (BYTE*)pPixels;
    for( INT y=0; y<nHeight; y++ )
    {
        fwrite( pBytes, rowSize - paddingBytes, 1, pFile );
        if( paddingBytes != 0 )
            fwrite( &dwZero, 1, paddingBytes, pFile );
        pBytes += rowSize;
    }
    fclose( pFile );

    return TRUE;
}


If I remember correctly, this code works with both 24- and 32-bit bitmaps, although you should always use 32-bit bitmaps since they are A LOT faster even if you don't need the alpha channel.

Edited by Amr0

Share this post


Link to post
Share on other sites

The problem was a simple one, 

 

file.open(Filename, std::ios::out)

 

needed to be

 

file.open(Filename, std::ios::out | std::ios::binary);

 

since it needs to write the data in binary mode.

Share this post


Link to post
Share on other sites

Please don't edit discussion topics as "solved".  As these are discussion boards rather than StackExchange-style question/answer boards, there may be other solutions or discussion that can follow that won't happen if you edit topics that way.

Share this post


Link to post
Share on other sites


If I remember correctly, this code works with both 24- and 32-bit bitmaps, although you should always use 32-bit bitmaps since they are A LOT faster even if you don't need the alpha channel.

 

I still have to argue with people who think that bitmaps can't have transparency.

 

There are alot of features of bitmaps that nobody supports though.

For example Microsoft allows for PNG and JPEG compression, but most of their software doesn't support it in a file (its used for GDI only). Its kind of funny how those two modes work, they have basically an entire complete PNG or JPEG file where the pixel data should be. (you can read in a JPEG file, stick it inside of a bitmap container in memory, and send it to GDI and it will render it)

Share this post


Link to post
Share on other sites

Its kind of funny how those two modes work, they have basically an entire complete PNG or JPEG file where the pixel data should be. (you can read in a JPEG file, stick it inside of a bitmap container in memory, and send it to GDI and it will render it)


Most likely to get around patent issues.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!