4

What I try to accomplish is really simple: adding an image to a menu item. I'm programming in C using the win32 API. The image / bitmap shows up but the background is white. What I want is to turn the white background into transparant.

I have been reading every information I could find including stackoverflow but the information seems inconsistent. Some say a bitmap cannot have any form of transparancy, whereas others say it can. See for example this question:

Simple way to show a bitmap next to a menu item in a CMenu with correct transparency

Both SetMenuItemBitmaps() and SetMenuItemInfo() give a white background. Above link says if the bitmap is in 32bpp with premultiplied alpha it should be shown correctly. So either it simply is not possible this way, or the bmp I use is in the wrong format. Can anyone give the definite answer to this problem. If it turns out it can't be done using SetMenuItemInfo(), what is then the easiest way of solving this problem? I try to avoid owner draw solutions as I feel it is a bit overkill. Besides, as far as I understand owner drawn solutions are hard to respect Windows theming.

menubitmap.rc:

#include "menubitmap.h"

ID_ICON             ICON    DISCARDABLE "menu1.ico"
ID_BITMAP_EXIT      BITMAP  DISCARDABLE "Exit-icn.bmp"

ID_MENU MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit",                       ID_FILE_EXIT
    END
END

menubitmap.c:

#include <windows.h>
#include "menubitmap.h"

const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_CREATE:
        {
            HBITMAP btmp;
            MENUITEMINFOA miinfo;
            HMENU menu;

            btmp=LoadBitmap((HINSTANCE) GetModuleHandle (NULL), MAKEINTRESOURCE(ID_BITMAP_EXIT));
            menu=GetMenu(hwnd);
            miinfo.cbSize=sizeof(MENUITEMINFO);
            if(!GetMenuItemInfo(menu,ID_FILE_EXIT,FALSE,&miinfo)){
                    printf("getmenuiteminfo failed\r\n");
            }else{
                    miinfo.fMask |= MIIM_BITMAP;
                    miinfo.hbmpItem=btmp;
                    if(SetMenuItemInfo(menu,ID_FILE_EXIT,FALSE,&miinfo)){
                            printf("setmenuiteminfo");
                    }
            }

        }
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_FILE_EXIT:
                    PostMessage(hwnd, WM_CLOSE, 0, 0);
                break;
            }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, Message, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = MAKEINTRESOURCE(ID_MENU);
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON), IMAGE_ICON, 16, 16, 0);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "A Menu",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Here you can find the bitmap

Here is the bitmap in bas64 format:

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
AA==
rob
  • 43
  • 5
  • if you use real 32bpp with premultiplied alpha - image correct shown – RbMm Feb 11 '20 at 21:51
  • Bitmaps [version 5](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header) support per-pixel alpha transparency. You are probably losing your alpha channel along the way. How are you loading the bitmap? – IInspectable Feb 11 '20 at 22:29
  • I'm using a resource: `ID_BITMAP_EXIT BITMAP DISCARDABLE "Exit-icn.bmp"` and loading it using `btmp=LoadBitmap((HINSTANCE) GetModuleHandle (NULL), MAKEINTRESOURCE(ID_BITMAP_EXIT));` So the transparency is lost in the resource? – rob Feb 12 '20 at 08:34
  • `LoadBitmap` is available for use in the operating systems specified in the Requirements section. It may be altered or unavailable in subsequent versions. Instead, use `LoadImage` and `DrawFrameContro`l.] You can upload your 32bpp bitmap for our test. I would appreciate it if you could provide [a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). – Strive Sun Feb 12 '20 at 09:31
  • Switching to `LoadImage()` doesn't make a difference. I have tried different programs to make the bmp. The result is either a white background or a black background. Inspecting the executable with resource hacker shows the bitmap transparent. According to the first 2 comments, it should show up correctly. How do I send you an MRE? – rob Feb 12 '20 at 10:52
  • You do not send a [mcve]. You post it into your question. That's how Stack Overflow works. As for bitmap resources, they don't touch your image data. It's just a 1:1 copy of your image file. Loading, on the other hand, is probably where things go wrong. We don't know how you are calling `LoadImage`, so we cannot provide much help. – IInspectable Feb 12 '20 at 17:30
  • I have added reproducible example code. If this code works for you then the conclusion must be that the bmp is wrong. – rob Feb 13 '20 at 11:20
  • Please also add the bitmap image. You can add an image to your question, hosted on imgur.com (you don't need an account with that service if you are using the browser-based editor to update your question). – IInspectable Feb 13 '20 at 11:54
  • The bmp is [here](https://imgur.com/A6PDnNC) – rob Feb 13 '20 at 12:12
  • The image appears to be a PNG, without an alpha channel. Did Imgur change that, or is that really the image you are using? – IInspectable Feb 13 '20 at 16:48
  • Imgur changed it... I will paste it in base64 format in the question. – rob Feb 13 '20 at 19:20

2 Answers2

2

Alpha transparency in Windows' GDI is a minefield. It was added very late, and only a handful of API calls are truly capable of handling a dedicated alpha channel. LoadBitmap is not one of those that support (or at least do not break) alpha transparency. The alpha channel gets lost when you're loading the image from the application's resources.

To fix that, you'll have to use LoadImage, passing in the correct flags. LR_CREATEDIBSECTION is the important one, as that preserves the alpha channel in the source bitmap.

The fix is as simple as replacing

LoadBitmap((HINSTANCE) GetModuleHandle (NULL), MAKEINTRESOURCE(ID_BITMAP_EXIT))

with

(HBITMAP)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_BITMAP_EXIT),
                   IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION)

With that in place you'll see the menu icon displayed with per-pixel alpha transparency:

Screenshot

IInspectable
  • 46,945
  • 8
  • 85
  • 181
0

You can manually change the background color of the bitmap to achieve transparency.

The code is as follows:

void swap_color(HBITMAP hbmp)
{
    if(!hbmp)
        return;
    HDC hdc = ::GetDC(HWND_DESKTOP);
    BITMAP bm;
    GetObject(hbmp, sizeof(bm), &bm);
    BITMAPINFO bi = { 0 };
    bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth = bm.bmWidth;
    bi.bmiHeader.biHeight = bm.bmHeight;
    bi.bmiHeader.biPlanes = 1;
    bi.bmiHeader.biBitCount = 32;

    std::vector<uint32_t> pixels(bm.bmWidth * bm.bmHeight);
    GetDIBits(hdc, hbmp, 0, bm.bmHeight, &pixels[0], &bi, DIB_RGB_COLORS);

    //assume that the color at (0,0) is the background color
    uint32_t color_old = pixels[0];

    //this is the new background color
    uint32_t bk = GetSysColor(COLOR_MENU);

    //swap RGB with BGR
    uint32_t color_new = RGB(GetBValue(bk), GetGValue(bk), GetRValue(bk));

    for (auto &pixel : pixels)
        if(pixel == color_old)
            pixel = color_new;

    SetDIBits(hdc, hbmp, 0, bm.bmHeight, &pixels[0], &bi, DIB_RGB_COLORS);
    ::ReleaseDC(HWND_DESKTOP, hdc);
}

The least code sample:

HMENU m_hMenu;
HBITMAP g_BitMap;
...
case WM_CONTEXTMENU:
        {           
            m_hMenu = CreatePopupMenu();
            g_BitMap = (HBITMAP)LoadImage(NULL, L"UNTITLED.bmp", IMAGE_BITMAP, 16, 16, LR_LOADFROMFILE);
            swap_color(g_BitMap);
            InsertMenu(m_hMenu, 1, MF_BYPOSITION | MF_POPUP, NULL, L"Windows");
            MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
            mii.fMask = MIIM_BITMAP;
            mii.hbmpItem = g_BitMap;
            SetMenuItemInfo(m_hMenu, 0, true, &mii);            
            TrackPopupMenu(m_hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_HORPOSANIMATION, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0, hWnd, NULL);   
        }
        break;

Debug:

1

The img from the link you mentioned, and I converted it to a 32-bit BMP picture.

Refer: How to show menu bitmaps with transparent background

Strive Sun
  • 5,988
  • 1
  • 9
  • 26
  • 1
    You've re-invented `LR_LOADTRANSPARENT`. That's a way to fake transparency, that leaves you with visual artifacts that don't exist with true alpha transparency. – IInspectable Feb 13 '20 at 10:35
  • 1
    @IInspectable Yes, this is a workaround.It seems that it is not easy to load 32-bit BMP transparent bitmap in menu items. If you have any idea, please feel free to let me know . – Strive Sun Feb 13 '20 at 10:47
  • @IInspectable Unless I am missing something, LR_LOADTRANSPARENT only uses COLOR_WINDOW. If for any reason your background color is not COLOR_WINDOW, then Strive Sun's answer becomes very relevant. – Reese Murdock Aug 10 '22 at 14:50
  • @ree It's a crutch no matter how you slice it. It's overly complex, and all that complexity buys you is that it works for a *single* background color. Contrast that with [my answer](https://stackoverflow.com/a/60222653/1889329) and you get **everything** this answer purports to provide, in a single line of code. Plus, it works for *any* background (solid color or otherwise). What's so relevant about this answer in your opinion? – IInspectable Aug 10 '22 at 16:34