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.