-3

This question could be considered more of a bug report on an unsightly and time-wasting issue I've recently encountered while using Win32/GDI:

That is, loading a bitmap image into static control (a bitmap static control, not icon). I'll demonstrate with the following code (this follows the creation of the main window):

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

We then create the control, which is a child of the main window:

/* 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. */

What is not impressive about this segment of code is the mandated use of LoadImage(...) to load the graphic from the program resources, where it is otherwise seemingly impossible to specify that our image will require transparency. Both flags LR_CREATEDIBSECTION and LR_LOADTRANSPARENT are required to effect this (once again, very ugly and not very explicit behavioural requirements. Why isn't LR_LOADTRANSPARENT good on its own?).

I will elaborate now that the bitmap has been tried at different bit-depths, each less than 16 bits per pixel (id est, using colour palettes), which incurs distractingly unaesthetical disuniformity between them. [Edit: See further discoveries in my answer]

What exactly do I mean by this?

A bitmap loaded at 8 bits per pixel, thus having a 256-length colour palette, renders with the first colour of the bitmap deleted (that is, set to the window class background brush colour); in effect, the bitmap is now 'transparent' in the appropriate areas. This behaviour is expected.

I then recompile the executable, now loading a similar bitmap but at (a reduced) 4 bits per pixel, thus having a 16-length colour palette. All is good and well, except I discover that the transparent region of the bitmap is painted with the WRONG background colour, one that does not match the window background colour. My wonderful bitmap has an unsightly grey rectangle around it, revealing its bounds.

What should the window background colour be? All documentation leads back, very explicitly, to this (HBRUSH)NULL-inclusive eyesore:

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

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

Where a certain colour preset must be incremented, then cast to a HBRUSH typename, to specify the desired background colour. 'Window colour' is an obvious choice, and a fragment of code very frequently recurring and reproducible.

You may note that when this is not done, the Window instead assumes the colour of its preceding number code, which on my system happens to be the 'Scroll' colour. Indeed, and alas, if I happen to forget the notorious and glorious +1 appended to the COLOR_WINDOW HBRUSH, my window will become the unintended colour of a scroll bar.

And it seems this mistake has propagated within Microsofts own library. Evidence? That a 4-bpp bitmap, when loaded, will also erase the bitmap transparent areas to the wrong background color, where a 8-bpp bitmap does not.

TL;DR

It seems the programmers at Microsoft themselves do not fully understand their own Win32/GDI interface jargon, especially regarding the peculiar design choice behind adding 1 to the Window Class WNDCLASS[EX] hbrBackground member (supposedly to support (HBRUSH)NULL).

This is unless, of course, anyone can spot a mistake on my part?

Shall I submit a bug report?

Many thanks.

Cara Ames
  • 255
  • 1
  • 11
  • 3
    The STATIC control is over 25 years old and very basic. Just write your own control for displaying your beautiful bitmap how you want it displayed. – Jonathan Potter Jun 29 '22 at 08:07
  • I'm not exactly sure what your complaint is about. That you [have to](https://devblogs.microsoft.com/oldnewthing/20061115-01/?p=28993) do `COLOR_WINDOW+1`? That you don't understand [why](https://devblogs.microsoft.com/oldnewthing/20140305-00/?p=1593) it has to be `+1`? That `LoadImage` has a [bug](https://www.codeproject.com/Articles/3654/The-LoadImage-function-doesn-t-load-correctly-a-4)? – GSerg Jun 29 '22 at 15:17
  • Please show a [mcve]. That includes the actual image data you are trying to load. – IInspectable Jun 30 '22 at 12:31
  • @GSerg COLOR_WINDOW+1 is unsightly (you'd think a macro would exist for this reason) but that's not exactly my complaint; it's more that the Windows static bitmap control doesn't consistently erase transparent areas of the loaded bitmap to the same colour, unless (see answer below) this is explicitly set in the window callback procedure. I'm asking whether this is a feature or a bug. – Cara Ames Jun 30 '22 at 13:37
  • @IInspectable Minimal reproducible example. It horrifies me to provide so much code. Compiling will reveal that the window background colour and the bitmap background colour are not the same. – Cara Ames Jun 30 '22 at 18:25
  • `LoadImage` cannot load PNG images. A [mcve] needs to be reproducible. Are you having difficulty understanding this idea? – IInspectable Jul 01 '22 at 12:22
  • Those are BMP images. Stack Overflow seems to have converted them to PNG, however I did specify that the images are BMP (feel free to ignore this). The solid magenta is cleared to transparent by `LoadImage` (or at least should be), the problem is that the cleared colour is not consistently reproducible without the fix listed in my answer. No offence to you but I'm not sure you understand my question. Perhaps I'll just verify my own answer even though I can't fully ascertain whether it's correct or not, because it did solve the problem in my case. I only wanted to know why. – Cara Ames Jul 12 '22 at 11:41
  • @IInspectable, dost thou need help to convert an internet PNG to a BMP? I wonder if I'd have *noticed* that `LoadImage` was failing to load a PNG? I might have asked a different question if I wasn't seeing my bitmap at all. – Cara Ames Jul 12 '22 at 11:45
  • Any sort of processing of input potentially changes the observed behavior, thus invalidating the *reproducibility* of a [mcve]. We need to see *your* input, not an arbitrary rendition of an arbitrary image processing tool. – IInspectable Jul 12 '22 at 14:41
  • Link to GitHub provided. I compiled using the MinGW GCC toolset (x86_64). – Cara Ames Jul 25 '22 at 21:59
  • I've removed the Minimally Reproducible Example that apparently didn't help you and I'll just keep my own answer since it is obviously sufficient and I needn't have bothered with you. – Cara Ames Aug 11 '22 at 15:52

1 Answers1

0

As though to patch over a hole in a parachute, there is a solution that produces consistency, implemented in the window callback procedure:

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

It turns out the problem was not reproducible when other transparent bitmaps of varying sizes (not only bit depths) were loaded.

This is horrible. I am not sure why this happens. Insights?


EDIT: All classes have been removed to produce a neat 'minimal reproducible example'.

Cara Ames
  • 255
  • 1
  • 11
  • 3
    You seem pretty vocal about telling everyone else how much they suck. This, in itself, isn't entirely bad, yet coming from someone that would readily call the default message processing routine for any given message **twice**, I'm not entirely convinced that that opinion needs to be heard. – IInspectable Jun 29 '22 at 08:42
  • Thank you, I just noticed the 9th line is meant to be a return statement (I'll amend this now). – Cara Ames Jun 30 '22 at 13:05
  • EDIT: All classes have now been removed to align with the minimally reproducible example you suggested. – Cara Ames Jun 30 '22 at 18:48