Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

per-pixel alpha blended GUI demo


  • Please log in to reply
48 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Should really start a new topic, but do you have any idea of how to have an image with transparencies put onto an invisible gui? So the png for example would appear to be its own gui.

So I'm starting a new topic. :p

For anyone with programming experience, this demonstration is based on one of these articles/demos:
Per Pixel Alpha Blend (C++)
Per Pixel Alpha Blend in C#
(I forget which one.)


A while ago I wrote a test script to demonstrate how to do this from AutoHotkey. It got rather messy, as I tried various things with it. (Various text effects with GDI; messy because GDI doesn't really support alpha-blending.) Here's a cleaned up version - a magnifying glass (just an icon, lol) that can be dragged around the screen:

Posted Image

[Edit: see Sean's post with added PNG support via GDI+]
; REQUIRES WINDOWS 2000 OR LATER

#NoEnv


Gui, -Caption +E0x80000 ; WS_EX_LAYERED

; Prevents later Gui,Show calls from auto-sizing the GUI.
; Center/default positioning doesn't work unless we explicitly
; size the GUI.  :-/  (It will be resized to the size of the icon later.)
Gui, Show, W1 H1 Hide

Gui, +LastFound


if !(h_icon := ExtractIcon("shell32.dll", 23, 48))
    ExitApp

GetIconSize(h_icon, width, height)


; Create a device context compatible with the screen.
if (hdcDest := DllCall("CreateCompatibleDC", "UInt", 0))
{
    ; Create a 32-bit bitmap to draw the icon onto.
    if (h_bm := CreateDIBSection(width, height, 32, hdcDest, pBits))
    {
        ; Select the new bitmap into the device context.
        if (bmOld := GDI_SelectObject(hdcDest, h_bm))
        {
            ; Draw the icon onto the window.
            DllCall("DrawIconEx"
                , "UInt", hdcDest ; hdc (destination device context)
                , "Int" , x+1       ; xLeft
                , "Int" , 0         ; yTop
                , "UInt", h_icon
                , "UInt", width
                , "UInt", height
                , "UInt", 0         ; istepIfAniCur
                , "UInt", 0         ; hbrFlickerFreeDraw = NULL (draw directly into buffer)
                , "UInt", 3)        ; diFlags = DI_NORMAL
            
            
            ; BLENDFUNCTION bf
            VarSetCapacity(bf, 4, 0)
            ;NumPut(0, bf, 0, "UChar") ; BlendOp := AC_SRC_OVER
            ;NumPut(0, bf, 1, "UChar") ; BlendFlags - must be zero
            NumPut(255, bf, 2, "UChar") ; SourceConstantAlpha := 255
            NumPut(1, bf, 3, "UChar") ; AlphaFormat := AC_SRC_ALHPA

            
            VarSetCapacity(size, 8, 0)
            NumPut(width, size, 0)
            NumPut(height, size, 4)
            
            hwnd := WinExist()
            
            DllCall("UpdateLayeredWindow"
                , "uint", hwnd
                , "uint", 0 ; hdcDst
                , "uint", 0 ; pptDst (pointer to new position, or NULL if not changing)
                , "uint", &size ; psize (pointer to new size, or NULL if not changing)
                , "uint", hdcDest ; hdcSrc (defines the layered window)
                , "uint", &ptSrc  ; pptSrc (pointer to location of the layer in the DC)
                , "uint", 0 ; crKey (not used if !(dwFlags&ULW_COLROKEY))
                , "uint", &bf ; pblend
                , "uint", 2) ; dwFlags := ULW_ALPHA (use pblend, not crKey)

            ; Reselect previous object (as per MSDN recommendation.)
            GDI_SelectObject(hdcDest, bmOld)
        }
        ; we no longer need the bitmap! (Windows has created a copy for the layered window)
        DllCall("DeleteObject", "uint", h_bm)
    }
    ; Done using the device context.
    DllCall("DeleteDC", "UInt", hdcDest)
}

OnMessage(0x201, "WM_LBUTTONDOWN")

Gui, Show
return

GuiClose:
GuiEscape:
ExitApp

; Allow click-drag to move the window.
WM_LBUTTONDOWN() {
    if (A_Gui) {
        Gui, +LastFound
        PostMessage, 0xA1, 2 ; WM_NCLBUTTONDOWN
    }
}


GDI_SelectObject(hdc, hgdiobj) {
    return DllCall("SelectObject", "uint", hdc, "uint", hgdiobj)
}

GDI_DeleteObject(hObject) {
    return DllCall("DeleteObject", "uint", hObject)
}

GetIconSize(h_icon, ByRef width, ByRef height)
{
    VarSetCapacity(ii, 20, 0)
    
    if (DllCall("GetIconInfo", "UInt", h_icon, "UInt", &ii))
    {
        hbmColor := NumGet(ii, 16)
        hbmMask  := NumGet(ii, 12)
        
        ret := GetBitmapSize(hbmColor, width, height)
        
        DllCall("DeleteObject", "UInt", hbmColor)
        DllCall("DeleteObject", "UInt", hbmMask)
        
        return ret
    }
    return false
}

GetBitmapSize(h_bitmap, ByRef width, ByRef height, ByRef bpp="")
{
    VarSetCapacity(bm, 24, 0) ; BITMAP
    if (!DllCall("GetObject", "UInt", h_bitmap, "Int", 24, "UInt", &bm))
        return false
    width  := NumGet(bm, 4, "int")
    height := NumGet(bm, 8, "int")
    bpp    := NumGet(bm,18, "ushort")
    return true
}

CreateDIBSection(w, h, bpp, hDC=0, ByRef ppvBits=0)
{
    hdcUsed := hDC ? hDC : DllCall("GetDC", "UInt", 0)
    if (hdcUsed)
    {
        VarSetCapacity(bi, 40, 0) ; BITMAPINFO(HEADER)
        NumPut(40, bi,  0)              ; biSize
        NumPut(1,  bi, 12, "UShort")    ; biPlanes
        NumPut(0,  bi, 16)              ; biCompression = BI_RGB (none)
        NumPut(w,  bi,  4)              ; biWidth
        NumPut(h,  bi,  8)              ; biHeight
        NumPut(bpp,bi, 14, "UShort")    ; biBitCount
        
        hbmp := DllCall("CreateDIBSection"
            , "UInt" , hDC
            , "UInt" , &bi   ; defines format, attributes, etc.
            , "UInt" , 0     ; iUsage = DIB_RGB_COLORS
            , "UInt*", ppvBits ; gets a pointer to the bitmap data
            , "UInt" , 0
            , "UInt" , 0)

        if (hdcUsed != hDC)
            DllCall("ReleaseDC", "UInt", 0, "UInt", hdcUsed)

        return hbmp
    }
    return 0
}


ExtractIcon(Filename, IconNumber, IconSize=0)
{
    return DllCall("LoadImage"
        ,"uint",DllCall("GetModuleHandle","str","shell32.dll")
        ,"uint",IconNumber,"uint",1,"int",IconSize,"int",IconSize,"uint",0)
}
It should work on Vista and XP; I'm not sure if the icon exists on earlier versions of Windows. The icon can easily be changed, anyway, by changing this line:
if !(h_icon := ExtractIcon("shell32.dll", 23, 48))
Icon 23 on Vista and XP is a magnifying glass. The "48" on the end is to specify the 48x48 icon.


The process is "basically":
[*:3ry97oy8]Create the GUI/window with the WS_EX_LAYERED extended style (+E0x80000).
[*:3ry97oy8]Create a GDI device context. (Knowing how to use this is more important than knowing what it is.)
[*:3ry97oy8]Create a 32-bit device independent bitmap.
[*:3ry97oy8]Select the bitmap into the device context.
[*:3ry97oy8]Using the device context, draw the icon onto the bitmap.
[*:3ry97oy8]Call UpdateLayeredWindow() to update the size and content of the window, using the device context.If you already have a bitmap (i.e. loaded from a PNG file), you can skip steps 3 and 5. To load PNG files, see Image conversions and capturing with GDI+.


It is important to note that layered windows cannot have controls. At least, they won't be visible. To have similar functionality, you'd either have to use two windows/GUIs (one layered + one with controls) or draw your own "controls."

Edit: Updated ExtractIcon() to use LoadImage (thanks to Sean.)
Edit: Fixed WM_LBUTTONDOWN() to use Gui,+LastFound instead of Gui,Default (oops.)
Edit: Fixed handle leak in CreateDIBSection (minor since it didn't apply to the example.)

tic
  • Members
  • 1934 posts
  • Last active: Dec 21 2015 01:05 PM
  • Joined: 22 Apr 2007
This is very cool Lexikos. A probelm that I couldnt possibly understand yet until I delve further into your code is that on win2000, it shows the image, but it can only be moved around the area it was originally created (so is bounded by itself when it was 1st created). Hopefully you understand that, but its a little hard to describe.....

So I run the ahk, and the icon appears. I hover over it and it starts moving, but the edges of it are cut off outside where it was 1st drawn.

My plan is to make an objectdock clone, and then improve upon it. (may take a while :wink: )

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I think I understand what you mean. Perhaps the window's position needs to be updated using UpdateLayeredWindow. Try adding this to the auto-execute section of the script:
OnMessage(0x3, "WM_MOVE")
and this elsewhere:
WM_MOVE(wParam, lParam, msg, hwnd) {
    if (A_Gui) {
        VarSetCapacity(p,8), NumPut(lParam,p)
        NumPut(NumGet(p,2,"short"),p,4,"int") ; Y
        NumPut(NumGet(p,0,"short"),p,0,"int") ; X
        DllCall("UpdateLayeredWindow","uint",hwnd,"uint",0,"uint",&p
            ,"uint",0,"uint",0,"uint",0,"uint",0,"uint",0,"uint",0)
    }
}
The third parameter of UpdateLayeredWindow is pptDst:

Pointer to a POINT structure that specifies the new screen position of the layered window. If the current position is not changing, pptDst can be NULL.

Moving the window with UpdateLayeredWindow doesn't seem to trigger WM_MOVE. Even if it did,

If a message arrives while its function is still running due to a previous arrival of the same message, the function will not be called again



Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
I was wondering why no one was interested in it...
Here is the code for PNG files.

sFile	:= "C:\test.png"	; Specify a real PNG file.

Gui, +LastFound -Caption +E0x80000
hGui := WinExist()

pToken	:= Gdip_Startup()
pImage	:= Gdip_LoadImageFromFile(sFile)
nW	:= Gdip_GetImageWidth(pImage)
nH	:= Gdip_GetImageHeight(pImage)

mDC	:= Gdi_CreateCompatibleDC(0)
mBM	:= Gdi_CreateDIBSection(mDC, nW, nH, 32)
oBM	:= Gdi_SelectObject(mDC, mBM)

Gdip_DrawImageRectI(pGraphics:=Gdip_CreateFromHDC(mDC), pImage, 0, 0, nW, nH)
DllCall("UpdateLayeredWindow", "Uint", hGui, "Uint", 0, "Uint", 0, "int64P", nW|nH<<32, "Uint", mDC, "int64P", 0, "Uint", 0, "intP", 0xFF<<16|1<<24, "Uint", 2)

GDI_SelectObject(mDC, oBM)
Gdi_DeleteObject(mBM)
Gdi_DeleteDC(mDC)

Gdip_DeleteGraphics(pGraphics)
Gdip_DisposeImage(pImage)
Gdip_Shutdown(pToken)

Gui, Show, Center W%nW% H%nH%
Return

GuiClose:
GuiEscape:
ExitApp


Gdi_CreateCompatibleDC(hDC = 0)
{
	Return	DllCall("gdi32\CreateCompatibleDC", "Uint", hDC)
}

Gdi_CreateDIBSection(hDC, nW, nH, bpp = 32, ByRef pBits = "")
{
	NumPut(VarSetCapacity(bi, 40, 0), bi)
	NumPut(nW, bi, 4)
	NumPut(nH, bi, 8)
	NumPut(bpp, NumPut(1, bi, 12, "UShort"), 0, "Ushort")
 
	Return	DllCall("gdi32\CreateDIBSection", "Uint", hDC, "Uint", &bi, "Uint", DIB_RGB_COLORS:=0, "UintP", pBits, "Uint", 0, "Uint", 0)
}

Gdi_SelectObject(hDC, hGdiObj)
{
	Return	DllCall("gdi32\SelectObject", "Uint", hDC, "Uint", hGdiObj)
}

Gdi_DeleteObject(hGdiObj)
{
	Return	DllCall("gdi32\DeleteObject", "Uint", hGdiObj)
}

Gdi_DeleteDC(hDC)
{
	Return	DllCall("gdi32\DeleteDC", "Uint", hDC)
}

Gdip_Startup()
{
	If Not	DllCall("GetModuleHandle", "str", "gdiplus")
		DllCall("LoadLibrary"    , "str", "gdiplus")
	VarSetCapacity(si, 16, 0), si := Chr(1)
	DllCall("gdiplus\GdiplusStartup", "UintP", pToken, "Uint", &si, "Uint", 0)
	Return	pToken
}

Gdip_Shutdown(pToken)
{
	DllCall("gdiplus\GdiplusShutdown", "Uint", pToken)
	If   hModule :=	DllCall("GetModuleHandle", "str", "gdiplus")
			DllCall("FreeLibrary"    , "Uint", hModule)
	Return	0
}

Gdip_CreateFromHDC(hDC)
{
	DllCall("gdiplus\GdipCreateFromHDC", "Uint", hDC, "UintP", pGraphics)
	Return	pGraphics
}

Gdip_DeleteGraphics(pGraphics)
{
	Return	DllCall("gdiplus\GdipDeleteGraphics", "Uint", pGraphics)
}

Gdip_LoadImageFromFile(sFile)
{
	VarSetCapacity(wFile, 1023)
	DllCall("kernel32\MultiByteToWideChar", "Uint", 0, "Uint", 0, "Uint", &sFile, "int", -1, "Uint", &wFile, "int", 512)
	DllCall("gdiplus\GdipLoadImageFromFile", "Uint", &wFile, "UintP", pImage)
	Return	pImage
}

Gdip_DisposeImage(pImage)
{
	Return	DllCall("gdiplus\GdipDisposeImage", "Uint", pImage)
}

Gdip_GetImageWidth(pImage)
{
	DllCall("gdiplus\GdipGetImageWidth", "Uint", pImage, "UintP", nW)
	Return	nW
}

Gdip_GetImageHeight(pImage)
{
	DllCall("gdiplus\GdipGetImageHeight", "Uint", pImage, "UintP", nH)
	Return	nH
}

Gdip_DrawImageRectI(pGraphics, pImage, nL, nT, nW, nH)
{
	Return	DllCall("gdiplus\GdipDrawImageRectI", "Uint", pGraphics, "Uint", pImage, "int", nL, "int", nT, "int", nW, "int", nH)
}


Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

h_icon := ExtractIcon("shell32.dll", 23, 48)

Do you have any particular reason using PrivateExtractIcons which seems to be abandoned by MS? Have you tried LoadImage instead?

h_icon := DllCall("LoadImage", "Uint", DllCall("GetModuleHandle", "str", "shell32.dll"), "Uint", 23, "Uint", 1, "int", 48, "int", 48, "Uint", 0)


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
AutoHotkey uses LoadImage for icon files and ExtractIcon for executables. Since ExtractIcon is limited to 16x16 or 32x32, I had assumed that LoadImage did not support loading icons from executables...

Thanks for the tip.

Now I wonder why Chris used ExtractIcon() at all...

ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
Hmm, it didn't work for me. I'm running XP, and I got a draggable magnifying glass, but it wasn't transparent at all.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Hmm. Works fine for me on two different XP machines.

It doesn't work (on any version of Windows, afaik) if the display depth is set to anything other than 32-bit. I think this is because the icon has no alpha channel. The solution is to manually calculate the alpha data for the bitmap (after drawing the icon onto it) or (I think) use GDI+ (which properly supports alpha blending) to draw the icon.

Since you said the icon was draggable, I assume you are already running at 32-bit. The icon is not drag-able at 16-bit (because pixels with alpha=0 are transparent to the mouse.) I think the magnifying glass is still visible because GDI uses pre-multiplied alpha.

SoggyDog
  • Members
  • 803 posts
  • Last active: Mar 04 2013 06:27 AM
  • Joined: 02 May 2006

Hmm, it didn't work for me. I'm running XP, and I got a draggable magnifying glass, but it wasn't transparent at all.

Same here; Draggable, but not transparant.
This is my computer at work; Haven't tried at home.

1024x768 32-bit 60 Hertz

MOBILITY RADEON AGP (0x4C59)
Internal DAC(350MHz)
16 MB

Microsoft Windows XP
Professional
Version 2002
Service Pack 2

Intel® Pentium® III
Mobile CPU 866MHz
863 MHz, 1.00 GB of RAM

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
I think the transparent magnifying glass icon is Vista only. The one in XP isn't transparent. You may test the per-pixel alpha-blending with the PNG file here:
<!-- m -->http://en.wikipedia....etwork_Graphics<!-- m -->

You may convert this .png to .ico, then could test also transparent icon for per-pixel alpha blending.

ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
Ah, yes. The PNG one is works.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Actually, it has slight transparency around the edges to make it look smooth. I can see how one might think it has no transparency. When you said "wasn't transparent at all", I imagined a completely opaque box. :roll:

Btw, you can also adjust the overall transparency level by changing this line:
NumPut(255, bf, 2, "UChar") ; SourceConstantAlpha := 255


ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007

Actually, it has slight transparency around the edges to make it look smooth. I can see how one might think it has no transparency.

Right again. Sorry. I was expecting the lens to be transparent like in your example.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Actually, it has slight transparency around the edges to make it look smooth.

As a matter of fact, it doesn't work with GdiPlus in XP while it does work with DrawIconEx. I suppose it's because GdiPlus in XP doesn't fully support Icon yet. I'm wondering if the GdiPlus in Vista improves the support for Icon.

;sFile	:= "C:\mag.ico"	; Specify the real path of the Icon.
;hIcon	:= DllCall("LoadImage", "Uint", 0, "Uint", &sFile, "Uint", 1, "int", 48, "int", 48, "Uint", 0x10)
hIcon	:= DllCall("LoadImage", "Uint", DllCall("GetModuleHandle", "str", "shell32.dll"), "Uint", 23, "Uint", 1, "int", 48, "int", 48, "Uint", 0)

pToken	:= Gdip_Startup()
pBitmap	:= hIcon ? Gdip_CreateBitmapFromHICON(hIcon) : Gdip_CreateBitmapFromFile(sFile)
hBitmap	:= Gdip_CreateHBITMAPFromBitmap(pBitmap)
nW	:= Gdip_GetImageWidth( pBitmap)
nH	:= Gdip_GetImageHeight(pBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)

DllCall("DestroyIcon", "Uint", hIcon)

mDC	:= Gdi_CreateCompatibleDC(0)
oBitmap	:= Gdi_SelectObject(mDC, hBitmap)

Gui, +LastFound -Caption +E0x80000
DllCall("UpdateLayeredWindow", "Uint", WinExist(), "Uint", 0, "Uint", 0, "int64P", nW|nH<<32, "Uint", mDC, "int64P", 0, "Uint", 0, "UintP", 255<<16|1<<24, "Uint", 2)
Gui, Show, Center W%nW% H%nH%

Gdi_SelectObject(mDC, oBitmap)
Gdi_DeleteObject(hBitmap)
Gdi_DeleteDC(mDC)
Return

GuiClose:
GuiEscape:
ExitApp


Gdi_CreateCompatibleDC(hDC = 0)
{
	Return	DllCall("gdi32\CreateCompatibleDC", "Uint", hDC)
}

Gdi_SelectObject(hDC, hGdiObj)
{
	Return	DllCall("gdi32\SelectObject", "Uint", hDC, "Uint", hGdiObj)
}

Gdi_DeleteObject(hGdiObj)
{
	Return	DllCall("gdi32\DeleteObject", "Uint", hGdiObj)
}

Gdi_DeleteDC(hDC)
{
	Return	DllCall("gdi32\DeleteDC", "Uint", hDC)
}

Gdip_Startup()
{
	If Not   DllCall("GetModuleHandle", "str", "gdiplus")
	DllCall("LoadLibrary"    , "str", "gdiplus")
	VarSetCapacity(si, 16, 0), si := Chr(1)
	DllCall("gdiplus\GdiplusStartup", "UintP", pToken, "Uint", &si, "Uint", 0)
	Return	pToken
}

Gdip_Shutdown(pToken)
{
	DllCall("gdiplus\GdiplusShutdown", "Uint", pToken)
	If   hModule :=	DllCall("GetModuleHandle", "str", "gdiplus")
			DllCall("FreeLibrary"    , "Uint", hModule)
	Return	0
}

Gdip_CreateBitmapFromFile(sFile)
{
	VarSetCapacity(wFile, 1023)
	DllCall("kernel32\MultiByteToWideChar", "Uint", 0, "Uint", 0, "Uint", &sFile, "int", -1, "Uint", &wFile, "int", 512)
	DllCall("gdiplus\GdipCreateBitmapFromFile", "Uint", &wFile, "UintP", pBitmap)
	Return	pBitmap
}

Gdip_CreateBitmapFromHICON(hIcon)
{
	DllCall("gdiplus\GdipCreateBitmapFromHICON", "Uint", hIcon, "UintP", pBitmap)
	Return	pBitmap
}

Gdip_CreateHBITMAPFromBitmap(pBitmap, ARGB = 0)
{
	DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Uint", pBitmap, "UintP", hBitmap, "Uint", ARGB)
	Return	hBitmap
}

Gdip_DisposeImage(pImage)
{
	Return	DllCall("gdiplus\GdipDisposeImage", "Uint", pImage)
}

Gdip_GetImageWidth(pImage)
{
	DllCall("gdiplus\GdipGetImageWidth", "Uint", pImage, "UintP", nW)
	Return	nW
}

Gdip_GetImageHeight(pImage)
{
	DllCall("gdiplus\GdipGetImageHeight", "Uint", pImage, "UintP", nH)
	Return	nH
}


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

I'm wondering if the GdiPlus in Vista improves the support for Icon.

It's the same on Vista. Bitmap.FromHicon() / CreateBitmapFromHICON() loses the alpha channel. Icon.FromHandle() + Graphics.DrawIcon() should work, but they don't have unmanaged GDI+ equivalents.

Here's what I've discovered via .NET Reflector:

Icon.FromHandle()
The Icon class seems to be a wrapper around a GDI icon. FromHandle() simply copies the handle to the Icon's private handle variable.

Graphics.DrawIcon()
If the Graphics object was created from an Image object, it does this:
this.DrawImage(icon.ToBitmap(), targetRect);
If the icon is 32-bit, Icon.ToBitmap() creates a 32-bit bitmap and copies the icon data to it. This seems similar to what this guy does to work around the FromHicon issue.

There appears to be a bug in GDI+ with regard to getting the bitmap
image of any icon which results in an image being use that is not the
correct pixel format. This seems to occur with any GDI+ function that
internally has to a convert an image to a bitmap. The solution below
addresses the issue by using GetIconInfo, creating a bitmap which will
contain the correct bits but wrong format, then copy the bitmap bits
to a new bitmap with the correctly format.

If the Graphics object was not created from an Image object, it (indirectly) calls (the native Win32 function) DrawIconEx(). :?