[英]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_CREATEDIBSECTION
和LR_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>
就像修补降落伞上的一个洞一样,有一个解决方案可以产生一致性,在窗口回调过程中实现:
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.