繁体   English   中英

Win32 GDI 调色板透明度错误

[英]Win32 GDI color palette transparency bug

这个问题可以被认为是我最近在使用 Win32/GDI 时遇到的一个难看且浪费时间的问题的错误报告:

也就是将位图图像加载到静态控件中(位图静态控件,而不是图标)。 我将使用以下代码进行演示(这在创建主窗口之后):

HBITMAP hbmpLogo;

/* Load the logo bitmap graphic, compiled into the executable file by a resource compiler */
hbmpLogo = (HBITMAP)LoadImage(
        wc.hInstance,             /* <-- derived from GetModuleHandle(NULL) */
        MAKEINTRESOURCE(ID_LOGO), /* <-- ID_LOGO defined in a header */
        IMAGE_BITMAP,
        0, 0,
        LR_CREATEDIBSECTION | LR_LOADTRANSPARENT);

/* We have a fully functioning handle to a bitmap at this line */
if (!hbmpLogo)
{
    /* Thus this statement is never reached */
    abort();
}

然后我们创建控件,它是主窗口的子窗口:

/* Add static control */
m_hWndLogo = CreateWindowExW(
        0,            /* Extended styles, not used */
        L"STATIC",    /* Class name, we want a STATIC control */
        (LPWSTR)NULL, /* Would be window text, but we would instead pass an integer identifier
                       * here, formatted (as a string) in the form "#100" (let 100 = ID_LOGO) */
        SS_BITMAP | WS_CHILD | WS_VISIBLE, /* Styles specified. SS = Static Style. We select
                                            * bitmap, rather than other static control styles. */
        32,  /* X */
        32,  /* Y */
        640, /* Width. */
        400, /* Height. */
        hMainParentWindow,
        (HMENU)ID_LOGO, /* hMenu parameter, repurposed in this case as an identifier for the
                         * control, hence the obfuscatory use of the cast. */
        wc.hInstance,   /* Program instance handle appears here again ( GetModuleHandle(NULL) )*/
        NULL);
if (!m_hWndLogo)
{
    abort(); /* Also never called */
}

/* We then arm the static control with the bitmap by the, once more quite obfuscatory, use of
 * a 'SendMessage'-esque interface function: */

SendDlgItemMessageW(
        hMainParentWindow, /* Window containing the control */
        ID_LOGO,           /* The identifier of the control, passed in via the HMENU parameter
                            * of CreateWindow(...). */
        STM_SETIMAGE,      /* The action we want to effect, which is, arming the control with the
                            * bitmap we've loaded. */
        (WPARAM)IMAGE_BITMAP, /* Specifying a bitmap, as opposed to an icon or cursor. */
        (LPARAM)hbmpLogo);    /* Passing in the bitmap handle. */

/* At this line, our static control is sufficiently initialised. */

这段代码没有令人印象深刻的是强制使用 LoadImage(...) 从程序资源中加载图形,否则似乎不可能指定我们的图像需要透明度。 需要标志LR_CREATEDIBSECTIONLR_LOADTRANSPARENT来实现这一点(再一次,非常丑陋且不是非常明确的行为要求。为什么LR_LOADTRANSPARENT本身就不好?)。

现在我将详细说明该位图已在不同的位深度下进行了尝试,每个像素小于 16 位(id est,使用调色板),这会在它们之间产生令人分心的不美观的不均匀性。 [编辑:在我的回答中查看更多发现]

我到底是什么意思?

以每像素 8 位加载的位图,因此具有 256 长度的调色板,在渲染时删除位图的第一种颜色(即设置为窗口类背景画笔颜色); 实际上,位图现在在适当的区域是“透明的”。 这种行为是预期的。

然后我重新编译可执行文件,现在加载一个类似的位图,但每像素(减少)4 位,因此有一个 16 长度的调色板。 一切都很好,除了我发现位图的透明区域涂有错误的背景颜色,一种与窗口背景颜色不匹配的颜色。 我美妙的位图周围有一个难看的灰色矩形,显示了它的边界。

窗口背景颜色应该是什么? 所有文档都非常明确地导致了这个(HBRUSH)NULL包含的碍眼问题:

WNDCLASSEX wc = {}; /* Zero initialise */
/* initialise various members of wc
 * ...
 * ... */

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); /* Here is the eyesore. */

必须增加某个颜色预设,然后转换为HBRUSH类型名称,以指定所需的背景颜色。 “窗口颜色”是一个显而易见的选择,并且代码片段非常频繁地重复和重现。

您可能会注意到,如果未完成此操作,则窗口会采用其前面数字代码的颜色,在我的系统上恰好是“滚动”颜色。 确实,唉,如果我碰巧忘记了附加到COLOR_WINDOW HBRUSH的臭名昭著的光荣+1 ,我的窗口将变成滚动​​条的意外颜色。

似乎这个错误已经在微软自己的库中传播开来。 证据? 加载 4-bpp 位图时,也会将位图透明区域擦除为错误的背景颜色,而 8-bpp 位图则不会。

TL;博士

似乎微软的程序员自己并不完全理解他们自己的 Win32/GDI 接口术语,特别是关于在窗口类WNDCLASS[EX] hbrBackground成员中添加1背后的特殊设计选择(据说是为了支持(HBRUSH)NULL )。

当然,除非有人能发现我的错误?

我要提交错误报告吗?

非常感谢。


编辑:有人要求提供一个最小的可重现示例。 所有来源应包括在下面:

/*****************
 *****************
 ** Resources.h **
 *****************
 *****************/

#ifndef RESOURCES_H
#define RESOURCES_H

// An enum {} apparently doesn't suffice!
#define ID_ICON         100
#define ID_LOGO         101
#define ID_LOGO_BMP     102
#define ID_LOGO_BMP_LQ  103

#endif /* RESOURCES_H */
/*************
 ************* compiled with:
 ** logo.rc **   windres --input=logo.rc --output=data/logo.res
 *************   output-format=coff
 *************/

#include "winuser.h"
#include "resources.h"

CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "data\\logo.exe.manifest"

ID_ICON         ICON    "data\\icon.ico"
ID_LOGO_BMP     BITMAP  "data\\logo-8bpp.bmp"
ID_LOGO_BMP_LQ  BITMAP  "data\\logo-4bpp.bmp"
/**************
 ************** compiled with:
 ** main.cpp **   x86_64-w64-mingw32-g++ main.cpp *.res -O3 -s -static
 **************   -lgdi32 -lcomctl32 -mwindows -o logo.exe
 **************/

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#define UNICODE
#include <cwchar>
#include <windows.h>
#include <commctrl.h>

#include "resources.h"

#define WND_NAME_MENU  L"Logo Window"
#define WND_NAME_CLASS L"LOGO_WINDOW"

#define DESTROY_WINDOW_SAFELY(window)\
    if (window) {\
        DestroyWindow(window);\
        window = (HWND)NULL;\
    }

/* Uncomment below to compile with WndProc fix */
//#define COMPILE_WITH_FIX



/************
** Globals **
************/

HINSTANCE   g_hInst = (HINSTANCE)NULL;
HWND        g_hWnd  = (HWND)     NULL;
HWND        g_hLogo = (HWND)     NULL;

long        g_lScrW,
            g_lScrH;

bool        g_bBmpExchange = false;



/*************************
** Forward Declarations **
*************************/

bool init(int nCmdShow);
void bye();
bool openWindow(int nCmdShow);

RECT suggestWindowRect();

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

bool armStaticControl(int rscID);
void displayError(const wchar_t *__restrict precede);



/****************
** Entry Point **
****************/

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg = {};
    BOOL bResult;
    
    
    /* In order to avoid C++ static clean-up. */
    atexit(bye);
    
    /* Initialising evil non-const globals (sorry). */
    g_hInst = hInst;
    
    
    
    /* This will initialise everything. */
    if (!init(nCmdShow)) return EXIT_FAILURE;
    
    
    
    /* Message loop */
    while ( (bResult = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 )
    {
        if (bResult < 0)
        {
            displayError(L"GetMessage() error");
            return EXIT_FAILURE;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage (&msg);
        }
    }
    
    return EXIT_SUCCESS;
}



bool init(int nCmdShow)
{
    /* More initialisation of evil non-const globals (sorry). */
    g_lScrW = GetSystemMetrics(SM_CXSCREEN);
    g_lScrH = GetSystemMetrics(SM_CYSCREEN);
    
    return openWindow(nCmdShow);
}

void bye()
{
    armStaticControl(-1); /* Releases any objects when arg is -1 */
    DESTROY_WINDOW_SAFELY(g_hLogo);
    DESTROY_WINDOW_SAFELY(g_hWnd);
}




LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wp, LPARAM lp)
{
    switch (uiMsg)
    {
    case WM_PAINT:
        if (hWnd == g_hWnd)
        {
            PAINTSTRUCT ps  = {};
            RECT        rct = {};
            
            HDC hDC;
            
            hDC = BeginPaint(hWnd, &ps);
            if (hDC)
            {
                TCHAR const *dt = g_bBmpExchange ?
                    TEXT("Press ENTER to swap between bit-depths!\n")
                    TEXT("Currently displaying 4-bit bitmap.")
                    :
                    TEXT("Press ENTER to swap between bit-depths!\n")
                    TEXT("Currently displaying 8-bit bitmap.")
                    ;
                
                DrawText(hDC, dt, -1, &rct, DT_NOCLIP);
                
                EndPaint(hWnd, &ps);
            }
            
            return 0;
        }
        break;
    
#ifdef COMPILE_WITH_FIX
    case WM_CTLCOLORSTATIC:
        if (lp == (LPARAM)g_hLogo)
        {
            SetBkMode((HDC)wp, TRANSPARENT);
            return (LRESULT)GetSysColorBrush(COLOR_WINDOW);
        }
        break;
#endif /* COMPILE_WITH_FIX */
    
    case WM_KEYDOWN:
        switch (wp)
        {
        case VK_RETURN:
            /* Toggle between high and low bit-depth bitmaps. */
            InvalidateRect(g_hWnd, NULL, TRUE);
            if ( (g_bBmpExchange = !g_bBmpExchange) )
                armStaticControl(ID_LOGO_BMP_LQ);
            else
                armStaticControl(ID_LOGO_BMP);
            return 0;
        }
        break;
    
    case WM_CLOSE:
        DestroyWindow(g_hWnd);
        return 0;
    
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    
    return DefWindowProc(hWnd, uiMsg, wp, lp);
}



bool initClass();

bool openWindow(int nCmdShow)
{
    RECT rct;
    
    
    
    /* Class initialisation */
    if (!initClass()) return false;
    
    
    /* Get suggested window rectangle and adjust.
     * This will only return a centred rectangle that is 3/4 the size of the
     * screen along x and y (not 3/4 of the geometric area). */
    rct = suggestWindowRect();
    
    if (!AdjustWindowRectEx(
        &rct,
        WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
            WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
        FALSE,
        WS_EX_OVERLAPPEDWINDOW))
    {
        displayError(L"Adjusting window rectangle failed");
    }
    rct.right  += GetSystemMetrics(SM_CXHSCROLL);
    rct.bottom += GetSystemMetrics(SM_CYVSCROLL);
    
    /* Create window */
    g_hWnd = CreateWindowEx(
        WS_EX_OVERLAPPEDWINDOW, /* Extended Styles */
        WND_NAME_CLASS, WND_NAME_MENU, /* Class + display names */
        WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, /* Styles */
        rct.left,             /* X */
        rct.top,              /* Y */
        rct.right - rct.left, /* Width */
        rct.bottom - rct.top, /* Height */
        NULL,                 /* Parent */
        NULL,                 /* Menu */
        g_hInst,              /* Instance */
        NULL);
    if (!g_hWnd)
    {
        displayError(L"Failed to create window");
        return false;
    }
    
    /* Create static bitmap control */
    g_hLogo = CreateWindowEx(
        0, /* Extended Styles */
        L"STATIC", NULL, /* Class + display names */
        SS_BITMAP | WS_CHILD | WS_VISIBLE, /* Styles */
        32,        /* X */
        32,        /* Y */
        0,         /* Width */
        0,         /* Height */
        g_hWnd,    /* Parent window */
        (HMENU)ID_LOGO,   /* hMenu, repurposed as ID */
        g_hInst,          /* Instance */
        NULL);
    if (!g_hLogo)
    {
        displayError(L"Failed to create logo static control");
        return false;
    }
    
    /* Arm with transparent bitmap */
    if (!armStaticControl(ID_LOGO_BMP)) return false;
    
    /* Finally, show and update the window */
    ShowWindow(g_hWnd, nCmdShow);
    UpdateWindow(g_hWnd);
    
    return true;
}

bool initClass()
{
    WNDCLASSEX wc =
    {
        sizeof(WNDCLASSEX),           /* cbSize */
        CS_HREDRAW | CS_VREDRAW,      /* style */
        WndProc,                      /* callback */
        0, 0,                         /* class & window extra space */
        g_hInst,                      /* handle to instance */
        (HICON)NULL,                  /* handle to program's icon */              
        
        /* handle to cursor image, to display when mouse enters */
        (HCURSOR)LoadImage( NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0,
                            LR_DEFAULTSIZE | LR_SHARED),
        
        (HBRUSH)(COLOR_WINDOW+1),       /* window background colour */
        WND_NAME_MENU,                  /* menu name */
        WND_NAME_CLASS,                 /* class name */
        (HICON)NULL,                    /* small icon */
    };
    
    INITCOMMONCONTROLSEX cc_ex =
    {
        sizeof(INITCOMMONCONTROLSEX),
        ICC_STANDARD_CLASSES,
    };
    
    
    
    /* Initialise common controls */
    if (!InitCommonControlsEx(&cc_ex))
    {
        displayError(L"Failed to initialise common controls");
        return false;
    }
    
    /* Load program icon */
    wc.hIcon = (HICON)LoadImage(
        GetModuleHandle(NULL),
        MAKEINTRESOURCE(ID_ICON), IMAGE_ICON,
        0, 0,
        LR_SHARED | LR_DEFAULTSIZE);
    if (!wc.hIcon)
    {
        displayError(L"Failed to load icon");
    }
    wc.hIconSm = wc.hIcon;
    
    /* Register main window class */
    if (!RegisterClassEx(&wc))
    {
        displayError(L"Failed to initialise window class");
        
        DestroyIcon(wc.hIcon);
        return false;
    }
    
    return true;
}



/* Call with -1 as argument to destroy bitmap */
bool armStaticControl(int rscID)
{
    static HGDIOBJ hOldObject = (HGDIOBJ)NULL;
    
    HBITMAP hLogoBMP, hOldBMP;
    
    
    /* Check first that we have created the control */
    if (!g_hLogo) return false;
    
    /* If we must release objects */
    if (rscID == -1)
    {
        if (hOldObject == (HGDIOBJ)NULL) return false;
        
        /* Load original GDI object back into static control. */
        hOldBMP = (HBITMAP)SendDlgItemMessage(
            g_hWnd,
            ID_LOGO,
            STM_SETIMAGE,
            (WPARAM)IMAGE_BITMAP,
            (LPARAM)hOldObject);
        
        /* Then destroy the bitmap */
        DeleteObject((HGDIOBJ)hOldBMP);
        
        return true;
    }
    
    
    
    /* Load static control bitmap */
    hLogoBMP = (HBITMAP)LoadImage(
        GetModuleHandle(NULL),
        MAKEINTRESOURCE(rscID),
        IMAGE_BITMAP,
        0, 0,
        LR_CREATEDIBSECTION | LR_LOADTRANSPARENT);
    if (!hLogoBMP)
    {
        displayError(L"Failed to load logo");
    }
    
    /* Arm static control with bitmap. */
    hOldBMP = (HBITMAP)SendDlgItemMessage(
        g_hWnd,
        ID_LOGO,
        STM_SETIMAGE,
        (WPARAM)IMAGE_BITMAP,
        (LPARAM)hLogoBMP);
    
    if (!hOldObject)    hOldObject = (HGDIOBJ)hOldBMP;
    else                DeleteObject((HGDIOBJ)hOldBMP);
    
    return true;
}



void displayError(const wchar_t *__restrict precede)
{
    /* I am aware that this procedure could accept uiErr as an argument
     * and this would probably be safer. */
    DWORD const uiErr = GetLastError();
    
    /* Hopefully big enough. */
    wchar_t errbuf[1024];
    
    /* Using a buffer pointer is probably slightly faster than calling
     * wcslen(). */
    wchar_t *pErr = errbuf;
    
    
    
    /* Nice formatter. The 'stop' is returned to us too. */
    pErr += wsprintf(pErr, L"%s [0x%x]: ", precede, uiErr);
    
    /* Print windows error at the 'stop'. */
    FormatMessageW(
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,  /* source / format */
        uiErr, /* Message ID */
        0,     /* language ID */
        pErr,
        1024 - (pErr - errbuf),  /* Size */
        NULL   /* Args */);
    
    /* Display error. */
    MessageBoxW(NULL, errbuf, L"Error", MB_ICONHAND);
}



RECT suggestWindowRect()
{
    RECT ret;
    
    /* Not (3/4) screen area, just width and height separately */
    ret.right  = (LONG)((size_t)g_lScrW*3)>>2; /* (3/4)screen_width */
    ret.bottom = (LONG)((size_t)g_lScrH*3)>>2; /* (3/4)screen_height */
    
    /* Centering */
    ret.left = (g_lScrW>>1) - (ret.right >>1);
    ret.top  = (g_lScrH>>1) - (ret.bottom>>1);
    
    ret.right  += ret.left;
    ret.bottom += ret.top;
    
    return ret;
}

显现:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="*"
    name="buggy.bugged.logo"
    type="win32"
/>
<description>Transparent Logo.</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="*"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>

8 位标志: 徽标 (8-bpp),洋红色是透明的

4 位标志: 徽标 (4-bpp),洋红色是透明的

就像修补降落伞上的一个洞一样,有一个解决方案可以产生一致性,在窗口回调过程中实现:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wp, LPARAM lp)
{
    /* ... */

    switch (uiMsg)
    {
    /* This message is sent to us as a 'request' for the background colour
     * of the static control. */
    case WM_CTLCOLORSTATIC:
        /* WPARAM will contain the handle of the DC */
        /* LPARAM will contain the handle of the control */
        if (lp == (LPARAM)g_hLogo)
        {
            SetBkMode((HDC)wp, TRANSPARENT);
            return (LRESULT)GetSysColorBrush(COLOR_WINDOW); /* Here's the magic */
        }
        break;
    }

    return DefWindowProc(hWnd, uiMsg, wp, lp);
}

事实证明,当加载其他不同大小(不仅是位深度)的透明位图时,该问题无法重现。

这太可怕了。 我不确定为什么会这样。 洞察力?


编辑:所有类都已被删除,以产生一个简洁的“最小可重现示例”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM