Creating a HBITMAP from glReadPixels

2019-09-02 15:08发布

问题:

I need to create a HBITMAP from data returned by a glReadPixels() call:

HDC hCompDC = CreateCompatibleDC(NULL);
HDC hDC = GetDC();

m_hClipboardBitmap = CreateCompatibleBitmap(hDC, size.cx, size.cy);

if ( m_hClipboardBitmap == NULL )
{
    throw runtime_error( "Unable to create bitmap." );
}

HBITMAP hOldBm = (HBITMAP) SelectObject( hCompDC, m_hClipboardBitmap );

int numberOfBytes = 4 * size.cx * size.cy; 
unsigned char *pPixelData = new unsigned char[numberOfBytes];

::glReadPixels(minimum.x, minimum.y, size.cx, size.cy, GL_BGRA, GL_UNSIGNED_BYTE, pPixelData);

I tried using:

BITMAPINFOHEADER header; 
header.biWidth = size.cx; 
header.biHeight = size.cy; 
header.biSizeImage = numberOfBytes; 
header.biSize = sizeof(BITMAPINFOHEADER); 
header.biPlanes = 1; 
header.biBitCount =  4 * 8; // RGBA 
header.biCompression = 0; 
header.biXPelsPerMeter = 0; 
header.biYPelsPerMeter = 0; 
header.biClrUsed = 0; 
header.biClrImportant = 0;

HANDLE handle = (HANDLE)::GlobalAlloc (GHND, sizeof(BITMAPINFOHEADER) + numberOfBytes); 

if(handle != NULL) 
{ 
    char *pData = (char *) ::GlobalLock((HGLOBAL)handle); 

    memcpy(pData,&header,sizeof(BITMAPINFOHEADER)); 
    memcpy(pData + sizeof(BITMAPINFOHEADER), pPixelData, numberOfBytes); 

    ::GlobalUnlock((HGLOBAL)handle);

    OpenClipboard(); 
    EmptyClipboard(); 
    SetClipboardData(CF_DIB, handle); 
    CloseClipboard();
}

And that pastes into mspaint OK (so the data is good) but how on earth do I get it into a HBITMAP?!?!

回答1:

Are you calling the function with the correct parameters. Check the documentation of the function: http://msdn.microsoft.com/en-us/library/dd183491(v=vs.85).aspx. Seems like you have swapped the parameter order and are passing a pointer to a pointer to the data.

-Timo



回答2:

Very old thread, but I wanted to give an answer, at least to keep it as a repository.

void WriteOpenGLPixelsToHBITMAP( HBITMAP dstHBITMAP, HDC dstDC, SIZE dims )
{

    BITMAPINFO bitmapInfo;
    {
        ::memset( &bitmapInfo, 0, sizeof( BITMAPINFO ) );
        bitmapInfo.bmiHeader.biSize        = sizeof( BITMAPINFOHEADER );
        bitmapInfo.bmiHeader.biPlanes      = 1;
        bitmapInfo.bmiHeader.biBitCount    = 32;
        bitmapInfo.bmiHeader.biCompression = BI_RGB;
        bitmapInfo.bmiHeader.biWidth       = dims.cx;
        bitmapInfo.bmiHeader.biHeight      = dims.cy;
        bitmapInfo.bmiHeader.biSizeImage   = dims.cx * dims.cy * 4; // Size 4, assuming RGBA from OpenGL
    }

    void    *bmBits = NULL;
    HDC     memDC   = ::CreateCompatibleDC( dstDC );
    HBITMAP memBM   = ::CreateDIBSection( NULL, &bitmapInfo, DIB_RGB_COLORS, &bmBits, NULL, 0 );


    ::glReadPixels( 0,
                    0,
                    dims.cx,
                    dims.cy,
                    GL_BGRA_EXT,
                    GL_UNSIGNED_BYTE,
                    bmBits );
    HGDIOBJ prevBitmap = ::SelectObject( memDC, memBM );
    HGDIOBJ obj        = ::SelectObject( dstDC, dstHBITMAP );

    // Remember that OpenGL origin is at bottom, left, but bitmaps are top, left
    if ( false == BitBlt( dstDC, 0 /*left*/, dims.cy /*top*/, dims.cx, dims.cy, memDC, 0, 0, SRCCOPY ) )
    {
        assert( false && "Failed to write pixels to HBitmap from OpenGL glReadPixels" );
    }

    ::SelectObject( memDC, prevBitmap );
    ::DeleteObject( memBM );
    ::DeleteDC( memDC );                    
}

As mentioned, be aware of image being inverted. You can swap SRCCOPY for SRCINVERT. Also you might want to make sure you are copying regions. The code above assumes that the region matches the viewport.