4

I would like to create 32 bit color icons programmatically using C++ and Win API. For this purpose I use the following code which I found here.

HICON CreateSolidColorIcon(COLORREF iconColor, int width, int height)
{
    // Obtain a handle to the screen device context.
    HDC hdcScreen = GetDC(NULL);

    // Create a memory device context, which we will draw into.
    HDC hdcMem = CreateCompatibleDC(hdcScreen);

    // Create the bitmap, and select it into the device context for drawing.
    HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
    HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, hbmp);

    // Draw your icon.
    // 
    // For this simple example, we're just drawing a solid color rectangle
    // in the specified color with the specified dimensions.
    HPEN hpen = CreatePen(PS_SOLID, 1, iconColor);
    HPEN hpenOld = (HPEN)SelectObject(hdcMem, hpen);
    HBRUSH hbrush = CreateSolidBrush(iconColor);
    HBRUSH hbrushOld = (HBRUSH)SelectObject(hdcMem, hbrush);
    Rectangle(hdcMem, 0, 0, width, height);
    SelectObject(hdcMem, hbrushOld);
    SelectObject(hdcMem, hpenOld);
    DeleteObject(hbrush);
    DeleteObject(hpen);

    // Create an icon from the bitmap.
    // 
    // Icons require masks to indicate transparent and opaque areas. Since this
    // simple example has no transparent areas, we use a fully opaque mask.
    HBITMAP hbmpMask = CreateCompatibleBitmap(hdcScreen, width, height);
    ICONINFO ii;
    ii.fIcon = TRUE;
    ii.hbmMask = hbmpMask;
    ii.hbmColor = hbmp;
    HICON hIcon = CreateIconIndirect(&ii);
    DeleteObject(hbmpMask);

    // Clean-up.
    SelectObject(hdcMem, hbmpOld);
    DeleteObject(hbmp);
    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdcScreen);

    // Return the icon.
    return hIcon;
}

In principle the code works and I can use it to create colored icons at runtime using the Win API. However, I have some problems and questions about that code (and creating icons in general) which I would like to discuss.

  • The icons created with this function don't seem to be of 32 bit color depth. If I use a color like RGB(218, 112, 214) I would expect it to be some light purple. However, the actual displayed color is gray. How can I change the code such that the color is really 32 bit RGB?
  • The icon created is completly filled with the color, I would like to have a thin black boarder around it... how can this be achieved?
  • In the MSDN documentation (a bit downwards) it is mentioned that "Before closing, your application must use DestroyIcon to destroy any icon it created by using CreateIconIndirect. It is not necessary to destroy icons created by other functions. " However, in the documentation for e.g. CreateIcon in MSDN it is said that "When you are finished using the icon, destroy it using the DestroyIcon function." which is pretty much a contradiction. When do I actually have to destroy the icon?
  • Do these rules then also apply when I add the icon to an image list and this list to a combobox? I.e. do I have to clean up the image list and each associated icon?

Any help is highly appreciated.

Community
  • 1
  • 1
SampleTime
  • 291
  • 3
  • 19
  • Hmm, that mask does not look like a proper mask. It is used in an AND operation so you could simply use hbmp instead. – Hans Passant Jan 08 '17 at 14:50
  • yes, for mask need use `CreateBitmap` with `cBitsPerPel==1` instead `CreateCompatibleBitmap`, for color also `CreateBitmap` with `cBitsPerPel==32` and with `RGB` you not using alpha – RbMm Jan 08 '17 at 14:59
  • Hm, but in the MSDN documentation of CreateBitmap it says that for colored bitmaps one should use CreateCompatibleBitmap instead of CreateBitmap for performance reasons, so shouldn't both work? I tried CreateBitmap(width, height, 1, 32, 0) for the bitmap and hbmpMask = CreateBitmap(width, height, 1, 1, 0) for the mask but nothing changed (I'm not sure however about the remaining parameters like lpvBits for example) – SampleTime Jan 08 '17 at 15:50

2 Answers2

6

When do I actually have to destroy the icon?

read about DestroyIcon

It is only necessary to call DestroyIcon for icons and cursors created with the following functions: CreateIconFromResourceEx (if called without the LR_SHARED flag), CreateIconIndirect, and CopyIcon. Do not use this function to destroy a shared icon. A shared icon is valid as long as the module from which it was loaded remains in memory. The following functions obtain a shared icon.

  • LoadIcon
  • LoadImage (if you use the LR_SHARED flag)
  • CopyImage (if you use the LR_COPYRETURNORG flag and the hImage parameter is a shared icon)
  • CreateIconFromResource
  • CreateIconFromResourceEx (if you use the LR_SHARED flag)

so you need call DestroyIcon for not shared icon, when you are finished using it

ComboBoxEx not destroy image list which you assign to it with CBEM_SETIMAGELIST - so this image list must be valid until ComboBoxEx valid and you must destroy it yourself later.

ImageList_AddIcon

Because the system does not save hicon, you can destroy it after the macro returns

in other words ImageList_AddIcon make copy of your icon, and you can destroy your original icon, after macro return

for create 32 bit color icon try code like this:

HICON CreateGradientColorIcon(COLORREF iconColor, int width, int height)
{
    HICON hIcon = 0;

    ICONINFO ii = { TRUE };

    ULONG n = width * height;

    if (PULONG lpBits = new ULONG[n])
    {
        PULONG p = lpBits;

        ULONG x, y = height, t;
        do 
        {
            x = width, t = --y << 8;
            do 
            {
                *p++ = iconColor | ((t * --x) / n << 24);
            } while (x);

        } while (y);

        if (ii.hbmColor = CreateBitmap(width, height, 1, 32, lpBits))
        {
            if (ii.hbmMask = CreateBitmap(width, height, 1, 1, 0))
            {
                hIcon = CreateIconIndirect(&ii);

                DeleteObject(ii.hbmMask);
            }

            DeleteObject(ii.hbmColor);
        }

        delete [] lpBits;
    }

    return hIcon;
}

when I draw (DrawIconEx(, DI_IMAGE|DI_MASK)) this icon over green mesh I view next:

enter image description here

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Thank you, just a remark: the function expects the colors in BGR format, not RGB (took me quite a bit to figure that out :D) – SampleTime Jan 09 '17 at 17:25
  • 1
    @SampleTime - yes, I confuse color order. really in `COLORREF iconColor` the lowest byte must be Blue. when `RGB` use Red as lowest byte. because in `RGB(218, 112, 214)` red and blue almost equal - I not note this – RbMm Jan 09 '17 at 17:41
4

To everyone who has stumbled upon this solution, I am simply posting a little bit more of a documented solution to RbMm's answer. This is basically the same as his solution (maybe not as performant, I'm not sure):

static HICON CreateIconFromBytes(HDC DC, int width, int height, uint32* bytes) {
        HICON hIcon = NULL;

        ICONINFO iconInfo = {
            TRUE, // fIcon, set to true if this is an icon, set to false if this is a cursor
            NULL, // xHotspot, set to null for icons
            NULL, // yHotspot, set to null for icons
            NULL, // Monochrome bitmap mask, set to null initially
            NULL  // Color bitmap mask, set to null initially
        };

        uint32* rawBitmap = new uint32[width * height];

        ULONG uWidth = (ULONG)width;
        ULONG uHeight = (ULONG)height;
        uint32* bitmapPtr = rawBitmap;
        for (ULONG y = 0; y < uHeight; y++) {
            for (ULONG x = 0; x < uWidth; x++) {
                // Bytes are expected to be in RGB order (8 bits each)
                // Swap G and B bytes, so that it is in BGR order for windows
                uint32 byte = bytes[x + y * width];
                uint8 A = (byte & 0xff000000) >> 24;
                uint8 R = (byte & 0xff0000) >> 16;
                uint8 G = (byte & 0xff00) >> 8;
                uint8 B = (byte & 0xff);
                *bitmapPtr = (A << 24) | (R << 16) | (G << 8) | B;
                bitmapPtr++;
            }
        }

        iconInfo.hbmColor = CreateBitmap(width, height, 1, 32, rawBitmap);
        if (iconInfo.hbmColor) {
            iconInfo.hbmMask = CreateCompatibleBitmap(DC, width, height);
            if (iconInfo.hbmMask) {
                hIcon = CreateIconIndirect(&iconInfo);
                if (hIcon == NULL) {
                    Log::Warning("Failed to create icon.");
                }
                DeleteObject(iconInfo.hbmMask);
            } else {
                Log::Warning("Failed to create color mask.");
            }
            DeleteObject(iconInfo.hbmColor);
        } else {
            Log::Warning("Failed to create bitmap mask.");
        }

        delete[] rawBitmap;

        return hIcon;
}

This solution will work with STB image library for loading images. So you can literally just load an image with stb, then pass the byte data to this function, and you will get an icon as a result. I had a little bit of trouble setting the icon as well, and eventually did this to get that to work:

HICON icon = CreateIconFromBytes(DC, image.m_Width, image.m_Height, image.m_Pixels);
SendMessage(WND, WM_SETICON, ICON_SMALL, (LPARAM)icon);
SendMessage(WND, WM_SETICON, ICON_BIG, (LPARAM)icon);
SendMessage(WND, WM_SETICON, ICON_SMALL2, (LPARAM)icon);

The only thing you should note about this is that you should probably use 3 different sized icons for the SendMessage() functions, but other than that this worked good for me :)

Edit: Here's the links to official MSDN documentation as well.
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconindirect
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatiblebitmap
https://learn.microsoft.com/en-us/windows/win32/menurc/using-icons