具有透明背景的win32菜单项位图

4
我试图完成的目标非常简单:将图像添加到菜单项中。我正在使用win32 API编程C语言。图像/位图显示出来,但背景是白色的。我想要的是将白色背景变成透明的。
我已经阅读了所有可以找到的信息,包括stackoverflow,但信息似乎不一致。有人说位图不能具有任何形式的透明度,而另一些人则说可以。例如,可以查看以下问题: 在CMenu中以正确的透明度显示位图旁边的简单方法

无论是SetMenuItemBitmaps()还是SetMenuItemInfo()都会给出白色背景。上面的链接说,如果位图是32bpp带有预乘alpha,则应该正确显示。所以要么这种方式根本不可能,要么我使用的bmp格式不正确。有人能给出这个问题的明确答案吗?如果使用SetMenuItemInfo()无法解决,那么最简单的解决方法是什么?我尝试避免所有者绘制解决方案,因为我觉得这有点过度杀伤力。此外,据我所知,所有者绘制解决方案很难尊重Windows主题。

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;
}

在这里你可以找到位图

以下是以base64格式编码的位图:

Qk12BgAAAAAAADYAAAAoAAAAFAAAABQAAAABACAAAAAAAEAGAADEDgAAxA4AAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAplgAAKZZABOp
XgBFqmAATapgAEyqYABMqmAATKpgAEyqYABMqmAATalfAEinWgAaploAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAKhcAACLKwADsm0Ck8SKBPnHjwT6x48E+sePBPrHjwT6x48E+sePBPrHjwT6xYwE
+7VxAqyeTAANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqFwAAJhBAAu6egXE1KUM/9GgC//Omwr/
zpsK/86bCv/Omwr/zpsK/9CfCv/Wpwz/v4EG2qFQAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACp
XAAAmEEADLt7CMTVphT/voEK6K9nAoaxagN6sWoDe7FqA3uvaAJ/unoI3NWmFP+/ggraoVAAHwAA
AAAAAAAAAAAAAC4nwQAvJ8ETKybAZSklxSKqRQAKvHwMxNaoHP++gA7SnkoAFahbAAAAAAAAp1sA
AI4yAAe5eQu61qcc/8CDDtqgTwAfAAAAAAAAAAAtJ8EAMSbAEyYnwZ4gJsD7KSbCW9pTAAe6eg3E
06Ig/71+D9OfTQAVqVwAAAAAAACoXAAAjjIAB7p5DrrXqST/wYQT2qBPAB8AAAAALSbBADEkwBYm
K8OjGTDF/RkuxP4rJ8Jgq0UACKpjC4OzcBCtq2QLjIxFLRIAAP8BQhG2AKhcAACNMQAHu3oRutmr
LP/ChRfaoE8AHy0mwQAvJL8ZJi7Fqhk4yv4TO8z/GTfK/yUvxc0kMMWuJTDEsSUvw7ElL8SxJS/F
sSgrw38wJMAIYz5sAIwwAAe7exS62qw0/8KGG9qgTwAfMiG9DScwxqYZQM//FEXS/xRE0f8URNH/
FUPQ/xVD0P8VQ9D/FUPQ/xVC0P8WQtD/IjTI2DIivxhJM5YAiy8AB7x8F7rcrjz/w4cf2qBOAB8y
IL0NJzDGqRpE0v8VTNb/FUzW/xVL1f8WSdX/FknU/xZJ1P8WSdT/FknU/xZJ1P8iN8rYMSG+GUkz
lgCLLgAHvHwaut2wRP/EiCPan04AHy0nwQAvJL8cJjLHrhpI1P4UUtr/GknV/yU0yNAlNMm0JjTH
tiUzxbcmNMe2JTTItykuxYUwIr8JYT1vAIotAAe9fR2637JL/8WJKNqfTgAfAAAAAC0nwQAwIr4Y
JzLIqBpL1v4bStX+KyrDYZc4EwmqYxOEtHIfrqtlFI2EQDQUBgD/AkAKsQCoXAAAiSwAB759ILrg
tFP/xYos2p9NAB8AAAAAAAAAAC0mwQAwIL0VJzPIoiE/z/sqLMVb1UkAB75/I8TdrlT/woQo051K
ABWpXAAAAAAAAKhcAACIKwAHvn4juuK1W//GizDan00AHwAAAAAAAAAAAAAAAC4mwAAuJL8UKyrD
ayooxiSmPgAKwYMqxOO4ZP/FiDDSm0cAFahbAAAAAAAAp1sAAIcpAAe+fya647dj/8eMNNqeTQAf
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqVwAAJI6AAzCgy3E5bps/8eLNuixahGGtG8WerRvFnu0
bxZ7smsSf8GCLNzlumz/yI042p5NAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoXAAAkTgAC8KE
MMTnvXb/47dv/9+wZ//fsGf/37Bn/9+wZ//fsGb/4rVt/+nAev/IjTzankwAHgAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAKhcAAB8GQADt3Mdk9GbUPnWo1n61qNZ+tajWfrWo1n61qNZ+tajWfrWoln6
055U+7t5JayaRgANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKVXAQClWAATql8FRathB02r
YQdMq2EHTKthB0yrYQdMq2EHTKthB02qYAZIplkBGqZaAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AA==

如果您使用带有预乘 Alpha 的真正 32bpp - 图像将被正确显示。 - RbMm
位图版本5支持每像素透明度。你可能在途中丢失了alpha通道。你是如何加载位图的? - IInspectable
我正在使用资源:ID_BITMAP_EXIT BITMAP DISCARDABLE "Exit-icn.bmp",并使用 btmp=LoadBitmap((HINSTANCE) GetModuleHandle (NULL), MAKEINTRESOURCE(ID_BITMAP_EXIT)); 进行加载。因此,资源中的透明度丢失了吗? - rob
LoadBitmap 可在需求部分指定的操作系统中使用。 它可能会在以后的版本中被更改或不可用。 相反,请使用 LoadImageDrawFrameControl。您可以上传您的32bpp位图进行测试。如果您能够提供“最小可复现示例”,我将不胜感激。 - Strive Sun
切换到LoadImage()没有任何区别。我尝试了不同的程序来制作bmp。结果要么是白色背景,要么是黑色背景。使用资源编辑器检查可执行文件时,位图是透明的。根据前两个评论,它应该正确显示。我如何向您发送MRE? - rob
显示剩余6条评论
2个回答

2
在Windows的GDI中使用Alpha透明度是一个充满陷阱的过程。这个功能添加得很晚,只有少数API调用实际上能够处理专用的Alpha通道。LoadBitmap不支持(或者至少不能在不破坏的情况下支持) Alpha透明度。当你从应用程序资源中加载图像时,Alpha通道会丢失。
为了解决这个问题,您必须使用LoadImage并传入正确的标志。 LR_CREATEDIBSECTION是最重要的标志,因为它保留了源位图中的Alpha通道。
修复问题就是将原来的代码替换掉。
LoadBitmap((HINSTANCE) GetModuleHandle (NULL), MAKEINTRESOURCE(ID_BITMAP_EXIT))

使用

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

有了这个设置,您将看到菜单图标以每个像素的透明度显示:

屏幕截图


0

您可以手动更改位图的背景颜色以实现透明效果。

代码如下:

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);
}

最简代码示例:
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;

调试:

1

你提到的图片来自链接,我将其转换为32位BMP图片

参考:如何显示带有透明背景的菜单位图


1
你重新发明了 LR_LOADTRANSPARENT。这是一种伪造透明度的方法,会留下视觉瑕疵,而真正的 alpha 透明度则不会有这些问题。 - IInspectable
1
@IInspectable 是的,这是一个解决方法。似乎在菜单项中加载32位BMP透明位图并不容易。如果您有任何想法,请随时告诉我。 - Strive Sun
@IInspectable 除非我漏掉了什么,LR_LOADTRANSPARENT 只使用 COLOR_WINDOW。如果由于任何原因您的背景颜色不是 COLOR_WINDOW,则 Strive Sun 的答案变得非常相关。 - Reese Murdock
@ree 无论如何,这都是一个拐杖。它过于复杂,而所有这些复杂性所带来的好处只是它适用于单一背景颜色。与我的答案相比,您可以在一行代码中获得所有此答案所提供的内容。此外,它适用于任何背景(纯色或其他)。在您看来,这个答案有什么相关性? - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接