Need help taking a screenshot

I’m looking for advice from someone who is knowledgable, or could become so, about .bmp files. I’m currently trying to make a function that will take a screenshot and output it as a .bmp file. I almost have it working (it works perfectly if the screen has dimensions that are multiples of 4), but I am having trouble padding the file properly. To my knowledge, the pixel array is supposed to be padded to have row sizes that are multiples of 4 bytes. The code I have so far is:


void ExportScreen()
{
    const int WindowWidth = glutGet(GLUT_WINDOW_WIDTH);
    const int WindowHeight = glutGet(GLUT_WINDOW_HEIGHT);
    const int PixelperMeterH = 1000.f*(float)glutGet(GLUT_SCREEN_HEIGHT)/glutGet(GLUT_SCREEN_HEIGHT_MM);
    const int PixelperMeterW = 1000.f*(float)glutGet(GLUT_SCREEN_WIDTH)/glutGet(GLUT_SCREEN_WIDTH_MM);

    GLubyte* Pixels = new GLubyte[ 3 * WindowWidth * WindowHeight ];
    glReadPixels( 0, 0, WindowWidth, WindowHeight, GL_BGR, GL_UNSIGNED_BYTE, Pixels );

    int NumPads = WindowWidth%4;
    vector<GLubyte> ImageData( Pixels, Pixels + (3*WindowWidth*WindowHeight) );
    vector<int> Indicies;
    for ( int i = 0; i < ImageData.size()/3; i++ )
    {
        if ( i%WindowWidth == WindowWidth - 1 )
        {
            Indicies.push_back(3*i+3);
        }
    }
    for ( int i = 0; i < Indicies.size(); i++ )
    {
        for ( int j = 0; j < NumPads; j++ )
        {
            ImageData.insert( ImageData.begin() + Indicies[i] + NumPads*i + j, 'N' );
        }
    }

    Pixels = new GLubyte[ ImageData.size() ];
    for ( int i = 0; i < ImageData.size(); i++ )
        Pixels[i] = ImageData[i];

    BMP_Header Header;
    BMP_DIB_Header DIB_Header;

    Header.Ident = 0x4D42;
    Header.ByteSize = ( 4 * floor( (24 * WindowWidth + 31)/32.f ) * WindowHeight ) + 54;
    Header.Empty = 0;
    Header.Useless = 0;
    Header.PA_Adress = 54;

    DIB_Header.ByteSize = 40;
    DIB_Header.PixelWidth = WindowWidth;
    DIB_Header.PixelHeight = WindowHeight;
    DIB_Header.Planes = 1;
    DIB_Header.BBP = 24;
    DIB_Header.CompressionMethod = 0;
    DIB_Header.ImageSize = Header.ByteSize - 54;
    DIB_Header.HorizRes = PixelperMeterW;
    DIB_Header.VertRes = PixelperMeterH;
    DIB_Header.Colors = 0;
    DIB_Header.ImportantColors = 0;

    stringstream Temp;
    int Current = 0;
    while ( 1 )
    {
        Temp.str("");
        Temp<<"Screenshots/Screenshot_"<<Current<<".bmp";
        ifstream Check( Temp.str().c_str() );
        if ( Check.is_open() )
            Current++;
        else
            break;
    }

    ofstream ScreenShot( Temp.str().c_str(), ostream::binary );

    WriteByte2( ScreenShot, Header.Ident );
    WriteByte4( ScreenShot, Header.ByteSize );
    WriteByte2( ScreenShot, Header.Empty );
    WriteByte2( ScreenShot, Header.Useless );
    WriteByte4( ScreenShot, Header.PA_Adress );
    WriteByte4( ScreenShot, DIB_Header.ByteSize );
    WriteByte4( ScreenShot, DIB_Header.PixelWidth );
    WriteByte4( ScreenShot, DIB_Header.PixelHeight );
    WriteByte2( ScreenShot, DIB_Header.Planes );
    WriteByte2( ScreenShot, DIB_Header.BBP );
    WriteByte4( ScreenShot, DIB_Header.CompressionMethod );
    WriteByte4( ScreenShot, DIB_Header.ImageSize );
    WriteByte4( ScreenShot, DIB_Header.HorizRes );
    WriteByte4( ScreenShot, DIB_Header.VertRes );
    WriteByte4( ScreenShot, DIB_Header.Colors );
    WriteByte4( ScreenShot, DIB_Header.ImportantColors );
    ScreenShot.write( (char*)Pixels, ImageData.size() );

    WritetoLog("Captured screen successfully");
}

Note that OpenGL also pads pixel data to a multiple of 4 bytes by default, although this can be changed with glPixelStorei(GL_PACK_ALIGNMENT). With the default 4-byte alignment, you should just be able to write the data returned by glReadPixels() directly to the file (preceded by the BMP headers), although you’ll need to allocate a larger buffer than “width * height * 3”, e.g.:


size_t stride = (WindowWidth * 3 + (4-1))/4*4;
GLubyte* Pixels = new GLubyte[ stride * WindowHeight ];

Wish I had known that sooner. I’ll have to do a little more research on functions next time I try something like this. Also, thank you for the advice. I have the function working correctly now.