I'm currently working on a small library that enables you to get icons from files and folders. Now, I don't care if it only works on win8+ (cause that's the place I'm going to use it), however, I've run in to a tiny problem with regards to transparency. If you take a look at the following image:
The one I generate (from my library) is to the left, windows explorer is to the right.
Now, as you might see, first off there is 2 black lines in the upper right of the one I generate, second, there is a difference in the background color. So what I'm wondering is this; is there no way to get the exact same image used by windows explorer, or am I simply doing it wrong?
My code (with exception to structs/externs etc. for shortness) bellow, entire code here.
public static class Icon
{
public static Image GetIcon(string fileName, int size)
{
IShellItem shellItem;
Shell32.SHCreateItemFromParsingName(fileName, IntPtr.Zero, Shell32.IShellItem_GUID, out shellItem);
IntPtr hbitmap;
((IShellItemImageFactory)shellItem).GetImage(new SIZE(size, size), 0x0, out hbitmap);
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
Gdi32.GetObjectDIBSection(hbitmap, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// zero out the RGB values for all pixels with A == 0
// (AlphaBlend expects them to all be zero)
for (int i = 0; i < dibsection.dsBmih.biWidth * dibsection.dsBmih.biHeight; i++)
{
IntPtr ptr = dibsection.dsBm.bmBits + (i * Marshal.SizeOf(typeof(RGBQUAD)));
var rgbquad = (RGBQUAD)Marshal.PtrToStructure(ptr, typeof(RGBQUAD));
if (rgbquad.rgbReserved == 0)
{
rgbquad.rgbBlue = 0;
rgbquad.rgbGreen = 0;
rgbquad.rgbRed = 0;
}
else
{
;
}
Marshal.StructureToPtr(rgbquad, ptr, false);
}
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
// get the HDCs and select the HBITMAP
Graphics graphics = Graphics.FromImage(bitmap);
IntPtr hdcDest = graphics.GetHdc();
IntPtr hdcSrc = Gdi32.CreateCompatibleDC(hdcDest);
IntPtr hobjOriginal = Gdi32.SelectObject(hdcSrc, hbitmap);
// render the bitmap using AlphaBlend
BLENDFUNCTION blendfunction = new BLENDFUNCTION(BLENDFUNCTION.AC_SRC_OVER, 0, 0xFF, BLENDFUNCTION.AC_SRC_ALPHA);
Gdi32.AlphaBlend(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, width, height, blendfunction);
// clean up
Gdi32.SelectObject(hdcSrc, hobjOriginal);
Gdi32.DeleteDC(hdcSrc);
graphics.ReleaseHdc(hdcDest);
graphics.Dispose();
Gdi32.DeleteObject(hbitmap);
return bitmap;
}
}
It seems copying pixel by pixel was the solution. The following seems to be pixel-perfect equal to the explorer one.
public static Image GetIcon(string fileName, int size)
{
IShellItem shellItem;
Shell32.SHCreateItemFromParsingName(fileName, IntPtr.Zero, Shell32.IShellItem_GUID, out shellItem);
IntPtr hbitmap;
((IShellItemImageFactory)shellItem).GetImage(new SIZE(size, size), 0x0, out hbitmap);
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
Gdi32.GetObjectDIBSection(hbitmap, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
for (int x = 0; x < dibsection.dsBmih.biWidth; x++)
{
for (int y = 0; y < dibsection.dsBmih.biHeight; y++)
{
int i = y * dibsection.dsBmih.biWidth + x;
IntPtr ptr = dibsection.dsBm.bmBits + (i * Marshal.SizeOf(typeof(RGBQUAD)));
var rgbquad = (RGBQUAD)Marshal.PtrToStructure(ptr, typeof(RGBQUAD));
if (rgbquad.rgbReserved != 0)
bitmap.SetPixel(x, y, Color.FromArgb(rgbquad.rgbReserved, rgbquad.rgbRed, rgbquad.rgbGreen, rgbquad.rgbBlue));
}
}
Gdi32.DeleteObject(hbitmap);
return bitmap;
}