使用 Windows API 将图标转换成位图

在不依赖任何第三方库、仅使用 Windows API 的前提下,需要将窗口图标(HICON)转换成作为菜单图标的位图(HBITMAP)。

起初使用 GetIconInfo() 函数:

c++HBITMAP IconToBitmap(HICON icon)
{
    ICONINFO iconInfo;
    GetIconInfo(icon, &iconInfo);
    DeleteObject(iconInfo.hbmMask);
    return iconInfo.hbmColor;
}

然而绘制的菜单图标会有黑色背景,并且无法调整位图大小。

再尝试用 DrawIconEx() 函数将图标绘制成位图:

c++HBITMAP IconToBitmap(HICON icon, int width = 0, int height = 0)
{
    // 如果未提供位图尺寸,则获取系统默认小图标尺寸
    if (!width) width = GetSystemMetrics(SM_CXSMICON);
    if (!height) height = GetSystemMetrics(SM_CYSMICON);
    
    HDC hdc = GetDC(NULL);
    HDC hdcMem = CreateCompatibleDC(hdc);
    
    // 创建兼容位图(DDB)
    HBITMAP hbmpMem = CreateCompatibleBitmap(hdcMem, width, height);
    if (hbmpMem) {
        auto oldObj = SelectObject(hdcMem, hbmpMem);
        DrawIconEx(hdcMem, 0, 0, icon, width, height, 0, NULL, DI_NORMAL);
        SelectObject(hdcMem, oldObj);
    }
    
    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdc);
    return hbmpMem;
}

可以定义位图大小了,但是菜单绘制的图标仍有黑色背景。

最后尝试用 CreateDIBSection() 函数替代 CreateCompatibleBitmap() 函数:

c++HBITMAP IconToBitmap(HICON icon, int width = 0, int height = 0)
{
    if (!width) width = GetSystemMetrics(SM_CXSMICON);
    if (!height) height = GetSystemMetrics(SM_CYSMICON);
    
    HDC hdc = GetDC(NULL);
    HDC hdcMem = CreateCompatibleDC(hdc);
    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    // 创建设备无关位图(DIB)
    HBITMAP hbmpMem = CreateDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, NULL, NULL, 0);
    if (hbmpMem) {
        auto oldObj = SelectObject(hdcMem, hbmpMem);
        DrawIconEx(hdcMem, 0, 0, icon, width, height, 0, NULL, DI_NORMAL);
        SelectObject(hdcMem, oldObj);
    }
    
    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdc);
    return hbmpMem;
}

成功绘制了透明背景的菜单图标。

另外,微软的开源项目 PowerTroys 源代码中也实现了一个 CreateBitmapFromIcon() 函数:

c++HBITMAP CreateBitmapFromIcon(_In_ HICON hIcon, _In_opt_ UINT width, _In_opt_ UINT height)
{
    HBITMAP hBitmapResult = NULL;

    // Create compatible DC
    HDC hDC = CreateCompatibleDC(NULL);
    if (hDC != NULL)
    {
        // Get bitmap rectangle size
        RECT rc = { 0 };
        rc.left = 0;
        rc.right = (width != 0) ? width : GetSystemMetrics(SM_CXSMICON);
        rc.top = 0;
        rc.bottom = (height != 0) ? height : GetSystemMetrics(SM_CYSMICON);

        // Create bitmap compatible with DC
        BITMAPINFO BitmapInfo;
        ZeroMemory(&BitmapInfo, sizeof(BITMAPINFO));

        BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        BitmapInfo.bmiHeader.biWidth = rc.right;
        BitmapInfo.bmiHeader.biHeight = rc.bottom;
        BitmapInfo.bmiHeader.biPlanes = 1;
        BitmapInfo.bmiHeader.biBitCount = 32;
        BitmapInfo.bmiHeader.biCompression = BI_RGB;

        HDC hDCBitmap = GetDC(NULL);

        HBITMAP hBitmap = CreateDIBSection(hDCBitmap, &BitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);

        ReleaseDC(NULL, hDCBitmap);

        if (hBitmap != NULL)
        {
            // Select bitmap into DC
            HBITMAP hBitmapOld = static_cast<HBITMAP>(SelectObject(hDC, hBitmap));
            if (hBitmapOld != NULL)
            {
                // Draw icon into DC
                if (DrawIconEx(hDC, 0, 0, hIcon, rc.right, rc.bottom, 0, NULL, DI_NORMAL))
                {
                    // Restore original bitmap in DC
                    hBitmapResult = static_cast<HBITMAP>(SelectObject(hDC, hBitmapOld));
                    hBitmapOld = NULL;
                    hBitmap = NULL;
                }

                if (hBitmapOld != NULL)
                {
                    SelectObject(hDC, hBitmapOld);
                }
            }

            if (hBitmap != NULL)
            {
                DeleteObject(hBitmap);
            }
        }

        DeleteDC(hDC);
    }

    return hBitmapResult;
}