Search code examples
c++windowswinapihbitmaplayered-windows

Draw HBITMAP onto layered window. What's wrong?


Hello and good day to everyone,

my final target is to draw a PNG file including alpha onto the screen - that means not into an own window but just somewhere on the desktop. The part to load PNG's into a HBITMAP works now (tested that in a diffrent way) but I don't manage to draw it including alpha.

As far as I've heard the best way to do this would be using alyered windows. So I wroked a lot to redo a couple of examples and tiny tutorials.

The following code compiles without problems and there do not prompt any messages (that means the showError("#") function is never called).

Yet there is nothing visible on the screen :/

Sorry that it is so long... Hope someone would like to look at it at least quickly..

LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);


int main(HINSTANCE hInstance)
{


    WNDCLASSEX WndClass;
    char sClassName[]  = "mainClass";
    WndClass.cbSize     = sizeof(WNDCLASSEX);
    WndClass.style      = NULL;
    WndClass.lpfnWndProc   = WndProc;//WndProc;
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hInstance  = hInstance;
    WndClass.hIcon      = NULL;
    WndClass.hCursor    = LoadCursor(NULL, IDC_ARROW);
    WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    WndClass.lpszMenuName  = NULL;
    WndClass.lpszClassName = sClassName;
    WndClass.hIconSm    = LoadIcon(NULL, IDI_APPLICATION);
    if (RegisterClassEx(&WndClass) == 0) showError("-1");





    HWND screen = CreateWindowEx(WS_EX_LAYERED,//WS_EX_LEFT
        "mainClass",
        "UpdateLayeredWind",
        WS_DISABLED | WS_VISIBLE,
        200,200,260,260,
        NULL /*eventuelly, GM window*/,
        NULL,
        hInstance,
        NULL);  


    if (screen == NULL) showError("0");




        HBITMAP img = LoadImageResource("D://ThreadDraw/ThreadDraw-test/ThreadDraw/test.png");
            if (img == NULL) showError("1");






    BLENDFUNCTION blend = {0};

    blend.AlphaFormat = AC_SRC_ALPHA;
    blend.SourceConstantAlpha = 155;

    POINT ptPos = {200,300};
    SIZE sizeWnd = {260,260};
    POINT ptPos2 = {200,300};


    ShowWindow(screen, SW_SHOW);



    while (1)
    {


        PAINTSTRUCT             ps;
        HDC                     hdc;
        BITMAP                  bitmap;
        HDC                     hdcMem;
        HGDIOBJ                 oldBitmap;

        hdc = BeginPaint(screen, &ps);

        hdcMem = CreateCompatibleDC(hdc);
        oldBitmap = SelectObject(hdcMem, img);

        GetObject(img, sizeof(bitmap), &bitmap);


        if (SetLayout(hdc,LAYOUT_RTL) == GDI_ERROR)
            showError("5");



            if (!BitBlt(hdc, 0, 0, 64, 64, hdcMem, 0, 0, SRCCOPY))
                showError("4");



            if (!UpdateLayeredWindow(screen,hdcMem,&ptPos,&sizeWnd,hdc,&ptPos2,RGB(255,255,255),&blend,ULW_ALPHA))//ULW_OPAQUE))
            showError("2");



        EndPaint(screen, &ps);

        SelectObject(hdcMem, oldBitmap);
        DeleteDC(hdcMem);


        Sleep(10);

    }



    return 0;
}



LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) 
{
    switch(Message) 
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, Message, wParam, lParam);
    }
    return 0;
}

By the way, if I use ULW_OPAQUE instead of ULW_ALPHA in UpdateLayeredWindow, then a right sized, black window appears so think the issue has to be something minimal related to the PAINTSTRUKT or BitBlt function.. Yet I tried a lot of ways without any change.

Hope someone can help. Thank you very much in advance!


Solution

  • This is mostly wrong. Your code should:

    • Create the layered window with CreateWindowEx.
    • Attach the bitmap to it with UpdateLayeredWindow.
    • Show the window with ShowWindow. Windows will take care of painting the layered window, so you don't need to handle WM_PAINT or call BeginPaint.
    • Enter a message loop.

    And that's it.

    If you're using Visual Studio, create a new Win32 Project and it will create a new project with a message loop for you.

    Update

    Here's a sample program that creates a transparent layered window. It needs a function to load a PNG as a transparent bitmap. And it has no error checking.

    int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
    {
        LPCTSTR szWindowClass = _T("TransparentClass");
    
        // Register class
        WNDCLASSEX wcex = {0};
    
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.lpfnWndProc    = DefWindowProc;
        wcex.hInstance      = hInstance;
        wcex.lpszClassName  = szWindowClass;
    
        RegisterClassEx(&wcex);
    
        HWND hWnd = CreateWindowEx(WS_EX_LAYERED, szWindowClass, 0, WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    
        int width;
        int height;
        HBITMAP hbmp = LoadPng(L"sample.png", &width, &height);
    
        HDC hdcScreen = GetDC(0);
        HDC hdc = CreateCompatibleDC(hdcScreen);
        ReleaseDC(0, hdcScreen);
        HBITMAP hbmpold = (HBITMAP)SelectObject(hdc, hbmp);
    
        POINT dcOffset = {0, 0};
        SIZE size = {width, height};
        BLENDFUNCTION bf;
        bf.BlendOp = AC_SRC_OVER;
        bf.BlendFlags = 0;
        bf.SourceConstantAlpha = 255;
        bf.AlphaFormat = AC_SRC_ALPHA;
        UpdateLayeredWindow(hWnd, 0, 0, &size, hdc, &dcOffset, 0, &bf, ULW_ALPHA);
        SelectObject(hdc, hbmpold);
        DeleteDC(hdc);
        DeleteObject(hbmp);
    
        ShowWindow(hWnd, SW_SHOW);
    
        MSG msg;
    
        // Main message loop:
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return (int)msg.wParam;
    }
    

    Another Update

    Here's some code to premultiply the red, green and blue values by alpha. It assumes that splash_image points to 32bpp ARGB data of size width*height.

    LPBYTE bits = (LPBYTE)splash_image;
    int size = width * height;
    for (int pixel = 0; pixel != size; ++pixel)
    {
        bits[0] = bits[0] * bits[3] / 255;
        bits[1] = bits[1] * bits[3] / 255;
        bits[2] = bits[2] * bits[3] / 255;
        bits += 4;
    }