I'm posting this to help anyone else out there with this issue that
has cost me a days work to research and figure out.
This will address all issues with alpha loss on Windows XP style icons
(32bppARGP format) with regard to Bitmap.FromHicon and
Graphics.DrawImage.
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.
The resulting bitmap will work with DrawImage to the screen or to a
Bitmap Graphics object. I haven't tested this with ImageList, it is
likely that this will solve problems with that as well since adding an
icon to an image list requires the flawed internal conversion to a
bitmap.
Below is the C# code that can be added to a class for a simple static
method to return a good bitmap from an icon. I only performs the
special steps if the icon is 32 bit, otherwise it using Bitmap.HIcon
which appears to work for other format.
public struct ICONINFO
{
public int fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
public struct BITMAP
{
public int bmType;
public int bmWidth;
public int bmHeight;
public int bmWidthBytes;
public short bmPlanes;
public short bmBitsPixel;
public int bmBits;
}
[DllImport("gdi32")]
public static extern int GetObject(IntPtr hObject, int nCount, out
BITMAP bitmap);
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hObject);
public static Bitmap AlphaBitmapFromIcon(Icon icon)
{
// (Overcome Flaw #1: GDI+ Bitmap.FromHICON mangles the alpha so the
..NET Bitmap.FromHicon will not work)
// Call Win32 to get icon info with contains the icon bitmap, return
null if invalid
ICONINFO iconInfo;
if (GetIconInfo(icon.Handle, out iconInfo)==0)
return null;
// Get BITMAP struct from icon bitmap
BITMAP bitmapData;
GetObject(iconInfo.hbmColor, Marshal.SizeOf(typeof(Win32.BITMAP)),
out bitmapData);
// If not a 32bpp then ok to use Bitmap.FromHicon
if (bitmapData.bmBitsPixel!=32)
{
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
return Bitmap.FromHicon(icon.Handle);
}
// Create .NET wrapped bitmap from ICONINFO bitmap
Bitmap wrapBitmap=Bitmap.FromHbitmap(iconInfo.hbmColor);
// (Overcome Flaw #2: Bitmap.FromHbitmap creates a bitmap with the
correct bits but wrong pixel format)
// Copy bit form flawed bitmap to new bitmap with correct format
BitmapData bmData=wrapBitmap.LockBits(new Rectangle(0,0,
wrapBitmap.Width, wrapBitmap.Height),
ImageLockMode.ReadOnly, wrapBitmap.PixelFormat);
Bitmap dstBitmap=new Bitmap(bmData.Width, bmData.Height,
bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
wrapBitmap.UnlockBits(bmData);
// Caller must dispose of bitmaps returned in ICONINFO from
GetIconInfo call
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
// Return corrected bitmap
return dstBitmap;