Question

I have an icon in memory (RGB or whatever format I want via PIL/Pillow) and I want to use it with

win32gui.Shell_NotifyIcon

which requires an HICON. How do I get my RGB pixels into an HICON?

Or, even though that's wasteful, I wouldn't even mind doing a round trip via a temporary file and reloading it using win32gui.LoadImage, something like this:

from PIL import Image
#create example image with empty pixels:
img = Image.frombuffer("RGB", (32, 32), "\0"*32*32*3, "raw", "RGB", 0, 1)
img = img.convert("P")
img = img.resize((64, 64), Image.ANTIALIAS)
img.save("C:\\temp.ico", format="bmp")
import win32gui, win32api, win32con
flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
win32gui.LoadImage(None, "C:\\temp.ico", win32con.IMAGE_ICON, 0, 0, flags)

But this fails with: pywintypes.error: (0, 'LoadImage', 'No error message available')

I just can't find a picture format that I can write to that can then be read back and used correctly by LoadImage in IMAGE_ICON mode. It seems that LoadImage only supports ICO files in that mode: I can read back PNG or BMP data as IMAGE_BITMAP, but the resulting handle cannot be used by Shell_NotifyIcon. Is there a way to convert the IMAGE into an HICON instead? (in memory)

Was it helpful?

Solution

I don't know how I missed that, but there is a function called CreateIcon which takes pixel data as input, and although this one is missing from pywin32's win32gui, there is a CreateIconIndirect instead, which is just as good.

So the code becomes simply (still using a temporary file):

img = win32gui.LoadImage(None, "C:\\tmp.bmp", win32con.IMAGE_BITMAP, 0, 0, flags)
pyiconinfo = (True, 0, 0, img, img)
hicon = win32gui.CreateIconIndirect(pyiconinfo)

To get the RGB pixels from a PIL image into a bitmap without using a temporary file, I've used this code (yes, it is very slow doing it this way, but it is relatively simple and easy to read, and I really could not care less about performance in this case):

def img_to_bitmap(image, pixel_value):
    hdc = win32gui.CreateCompatibleDC(0)
    dc = win32gui.GetDC(0)
    hbm = win32gui.CreateCompatibleBitmap(dc, size, size)
    hbm_save = win32gui.SelectObject(hdc, hbm)
    for x in range(size):
        for y in range(size):
            pixel = image.getpixel((x, y))
            v = pixel_value(pixel)
            win32gui.SetPixelV(hdc, x, y, v)
    win32gui.SelectObject(hdc, hbm_save)
    win32gui.ReleaseDC(self.hwnd, hdc)
    win32gui.ReleaseDC(self.hwnd, dc)
    return hbm

For those that care about performance, a better solution probably involves CreateDIBitmap or SetDIBits

The more complete code can now be found in set_icon_from_data method of this tray icon wrapper class, it deals with splitting the alpha channel into its own bitmap so we can display transparent images properly too.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top