3
votes

I created a layered window (with WS_EX_LAYERED), size of about 400X300 px.
When drawing the window (using UpdateLayeredWindow) everything works great.

The problem is that I'm unable to get the HBITMAP of the window after drawing it. When trying to get the HBITMAP through the window's HDC, I get an empty (black) bitmap, the size of my entire desktop (1920X1080 px insted of 400X300 px).

Does anybody know if it is even possible to get the HDC\HBITMAP of a layered window?

Code samples

Here's the code of how I draw the layered window (again, works great):

void MyLayeredWindow::DrawLayered() const
{
    RECT rcWindow;
    GetWindowRect(rcWindow);

    int nWidth = abs(rcWindow.right - rcWindow.left);
    int nHeight = abs(rcWindow.bottom - rcWindow.top);

    // Create 32Bit bitmap to apply transparency 
    // (have to set negative height because if not the (0,0) point would be the bottom left instead of top left)
    VOID *ppvBits = NULL;
    BITMAPINFO BitmapInfo = {0};
    BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    BitmapInfo.bmiHeader.biWidth = nWidth;
    BitmapInfo.bmiHeader.biHeight = -nHeight;
    BitmapInfo.bmiHeader.biPlanes = 1;
    BitmapInfo.bmiHeader.biBitCount = 32;
    BitmapInfo.bmiHeader.biCompression = BI_RGB;

    // Copy view buffer to a temp DC and bitmap
    HDC hDcTemp = ::CreateCompatibleDC(NULL);
    assert(hDcTemp);
    HBITMAP hBitmapTemp = ::CreateDIBSection(hDcTemp, &BitmapInfo, DIB_RGB_COLORS, &ppvBits, NULL, 0);
    assert(hBitmapTemp && hBitmapTemp!=(HBITMAP)ERROR_INVALID_PARAMETER)
    ::SelectObject(hDcTemp, hBitmapTemp);

    // Darwing the window's conent here
    // ....
    // ....

    // Call UpdateLayeredWindow
    BLENDFUNCTION blend = {0};
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 190;
    blend.AlphaFormat = AC_SRC_ALPHA;

    SIZE sizeWnd = {0};
    sizeWnd.cx = nWidth;
    sizeWnd.cy = nHeight;
    POINT ptPos = {0};
    ptPos.x = rcWindow.left;
    ptPos.y = rcWindow.top;
    POINT ptSrc = {0,0};

    ::UpdateLayeredWindow(m_hWnd, NULL, &ptPos, &sizeWnd, hDcTemp, &ptSrc, 0, &blend, ULW_ALPHA);

    if(hDcTemp)
        ::DeleteDC(hDcTemp);

    if(hBitmapTemp)
        ::DeleteObject(hBitmapTemp);
}

Here's the code of how I capture the window's bitmap and save it to a file: (NOTICE: It works of "normal" windows, such as the Calculator)

bool MyLayeredWindow::SaveBitmapFile(__in const HWND& hWnd, __in const wstring& sFileName)
{
    HDC hDC = ::GetDC(hWnd);

    // get bitmap of DC
    HBITMAP hBmp = (HBITMAP)::GetCurrentObject( hDC, OBJ_BITMAP );

    // get info of bitmap
    BITMAPINFO stBmpInfo; 
    stBmpInfo.bmiHeader.biSize = sizeof( stBmpInfo.bmiHeader );
    stBmpInfo.bmiHeader.biBitCount = 0;
    GetDIBits( hDC, hBmp, 0, 0, NULL, &stBmpInfo, DIB_RGB_COLORS );

    // init info size
    ULONG iBmpInfoSize; 
    switch( stBmpInfo.bmiHeader.biBitCount )
    { 
    case 24:
        iBmpInfoSize = sizeof(BITMAPINFOHEADER); 
        break;
    case 16: 
    case 32:
        iBmpInfoSize = sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3; 
        break;
    default:
        iBmpInfoSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * ( 1 << stBmpInfo.bmiHeader.biBitCount ); 
        break;
    } 

    // copy header
    PBITMAPINFO pstBmpInfo = NULL;
    if( iBmpInfoSize != sizeof(BITMAPINFOHEADER) ) 
    { 
        pstBmpInfo = (PBITMAPINFO)GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT, iBmpInfoSize );
        PBYTE pbtBmpInfoDest = (PBYTE)pstBmpInfo; 
        PBYTE pbtBmpInfoSrc = (PBYTE)&stBmpInfo; 
        ULONG iSizeTmp = sizeof( BITMAPINFOHEADER );

        while( iSizeTmp-- ) 
            *( ( pbtBmpInfoDest )++ ) = *( ( pbtBmpInfoSrc )++ ); 
    } 

    // create file
    HANDLE hFile = CreateFile( sFileName.c_str(), GENERIC_WRITE, 0 , NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL );

    // init bmp file header
    BITMAPFILEHEADER stBmpFileHder = {0}; 
    stBmpFileHder.bfType = 0x4D42; // 'BM' 
    stBmpFileHder.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + iBmpInfoSize + pstBmpInfo->bmiHeader.biSizeImage; 
    stBmpFileHder.bfReserved1 = 0;
    stBmpFileHder.bfReserved2 = 0; 
    stBmpFileHder.bfOffBits = sizeof(BITMAPFILEHEADER) + iBmpInfoSize; 

    // write header to file
    DWORD dRet;
    WriteFile( hFile, (LPCVOID)&stBmpFileHder, sizeof(BITMAPFILEHEADER), &dRet, NULL );

    // allocate size for rest of bmp (body)
    PBYTE pBits = (PBYTE)GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT, stBmpInfo.bmiHeader.biSizeImage );

    // get bmp bits
    HBITMAP hBmpOld; 
    HBITMAP hTmpBmp = CreateCompatibleBitmap( hDC, pstBmpInfo->bmiHeader.biWidth, pstBmpInfo->bmiHeader.biHeight );
    hBmpOld = (HBITMAP)SelectObject( hDC, hTmpBmp ); 
    GetDIBits( hDC, hBmp, 0, pstBmpInfo->bmiHeader.biHeight, (LPSTR)pBits, pstBmpInfo, DIB_RGB_COLORS );

    // write bmp info
    WriteFile( hFile, (LPCVOID)pstBmpInfo, iBmpInfoSize, &dRet, NULL );

    // write bmp bits
    WriteFile( hFile, (LPCVOID)pBits, pstBmpInfo->bmiHeader.biSizeImage, &dRet, NULL );

    // release handles and free memory
    SelectObject( hDC, hBmpOld ); 
    DeleteObject( hTmpBmp ); 
    CloseHandle( hFile ); 
    GlobalFree( pstBmpInfo ); 
    GlobalFree( pBits ); 
    ReleaseDC( hWnd, hDC );

    return true;
}

Thanks!

1
The approach is in general wrong and can only work by accident. A window doesn't have a bitmap, you need to use BitBlt or PrintWindow. Use the CAPTURE_BLT option for a layered window. msdn.microsoft.com/en-us/library/windows/desktop/…Hans Passant
BitBlt and PrintWindow doesn't seem to apply to my layered window. It returns a blank (black\transparent) DC.Bagelzone Ha'bonè

1 Answers

0
votes

Since I didn't get any better answer, I simply called a "Draw" function that paints my layered window, onto a temporary HDC.

Meaning I don't copy existing bitmap, but create an identical one, using the same drawing function.

I'd still love to get a better answer for this question.