R – WinForms .NET 2.0: How to paint the proper sized icon

graphicswindows-vistawinforms

i have an ico file that contains a 48×48 and a 256×256 Vista PNG version (as well as the 32×32 and 16×16 versions). i want to draw the icon using the appropriate internal size version.

i've tried:

Icon ico = Properties.Resources.TestIcon;
e.Graphics.DrawIcon(ico, new Rectangle(0, 0, 48, 48));
e.Graphics.DrawIcon(ico, new Rectangle(48, 0, 256, 256));

But they draw the 32×32 version blown up to 48×48 and 256×256 respectively.

i've tried:

Icon ico = Properties.Resources.TestIcon;
e.Graphics.DrawIconUnstretched(ico, new Rectangle(0, 0, 48, 48));
e.Graphics.DrawIconUnstretched(ico, new Rectangle(48 0, 256, 256));

But those draw the 32×32 version unstretched.

i've tried:

Icon ico = Properties.Resources.TestIcon;
e.Graphics.DrawImage(ico.ToBitmap(), new Rectangle(0, 0, 48, 48));
e.Graphics.DrawImage(ico.ToBitmap(), new Rectangle(48, 0, 256, 256));

But those draw a stretched version of the 32×32 icon.

How do i make the icon draw itself using the appropriate size?

Additionally, i want to draw using the 16×16 version. i've tried:

Icon ico = Properties.Resources.TestIcon;
e.Graphics.DrawIcon(ico, new Rectangle(0, 0, 16, 16));
e.Graphics.DrawIconUnstretched(ico, new Rectangle(24, 0, 16, 16));
e.Graphics.DrawImage(ico.ToBitmap(), new Rectangle(48, 0, 16, 16));

But all those use the 32×32 version scaled down, except for the Unstretched call, which crops it to 16×16.

How do i make the icon draw itself using the appropriate size?


Following schnaader's suggestion of constructing a copy of the icon with the size you need doesn't work for 256×256 size. i.e. the following does not work (it uses a scaled version of the 48×48 icon):

e.Graphics.DrawIcon(
        new Icon(ico, new Size(256, 256)), 
        new Rectangle(0, 0, 256, 256));

While the following two do work:

e.Graphics.DrawIcon(
        new Icon(ico, new Size(16, 16)), 
        new Rectangle(0, 0, 16, 16));

e.Graphics.DrawIcon(
        new Icon(ico, new Size(48, 48)), 
        new Rectangle(0, 0, 48, 48));

Best Answer

Today, I made a very nice function for extracting the 256x256 Bitmaps from Vista icons.

I use it to display the large icon ( 256x256 ) as a Bitmap in "About" box. For example, this code gets Vista icon as PNG image, and displays it in a 256x256 PictureBox:

picboxAppLogo.Image = ExtractVistaIcon(Icon.ExtractAssociatedIcon(myIcon));

This function takes Icon object as a parameter. So, you can use it with any icons - from resources, from files, from streams, and so on. (Read below about extracting EXE icon).

It runs on any OS, because it does not use any Win32 API, it is 100% managed code :-)

// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx
// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx

Bitmap ExtractVistaIcon(Icon icoIcon)
{
    Bitmap bmpPngExtracted = null;
    try
    {
        byte[] srcBuf = null;
        using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
            { icoIcon.Save(stream); srcBuf = stream.ToArray(); }
        const int SizeICONDIR = 6;
        const int SizeICONDIRENTRY = 16;
        int iCount = BitConverter.ToInt16(srcBuf, 4);
        for (int iIndex=0; iIndex<iCount; iIndex++)
        {
            int iWidth  = srcBuf[SizeICONDIR + SizeICONDIRENTRY * iIndex];
            int iHeight = srcBuf[SizeICONDIR + SizeICONDIRENTRY * iIndex + 1];
            int iBitCount   = BitConverter.ToInt16(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 6);
            if (iWidth == 0 && iHeight == 0 && iBitCount == 32)
            {
                int iImageSize   = BitConverter.ToInt32(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 8);
                int iImageOffset = BitConverter.ToInt32(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 12);
                System.IO.MemoryStream destStream = new System.IO.MemoryStream();
                System.IO.BinaryWriter writer = new System.IO.BinaryWriter(destStream);
                writer.Write(srcBuf, iImageOffset, iImageSize);
                destStream.Seek(0, System.IO.SeekOrigin.Begin);
                bmpPngExtracted = new Bitmap(destStream); // This is PNG! :)
                break;
            }
        }
    }
    catch { return null; }
    return bmpPngExtracted;
}

IMPORTANT! If you want to load this icon directly from EXE file, then you CAN'T use Icon.ExtractAssociatedIcon(Application.ExecutablePath) as a parameter, because .NET function ExtractAssociatedIcon() is so stupid, it extracts ONLY 32x32 icon!

Instead, you better use the whole IconExtractor class, created by Tsuda Kageyu (http://www.codeproject.com/KB/cs/IconExtractor.aspx). You can slightly simplify this class, to make it smaller. Use IconExtractor this way:

// Getting FILL icon set from EXE, and extracting 256x256 version for logo...
using (TKageyu.Utils.IconExtractor IconEx = new TKageyu.Utils.IconExtractor(Application.ExecutablePath))
{
    Icon icoAppIcon = IconEx.GetIcon(0); // Because standard System.Drawing.Icon.ExtractAssociatedIcon() returns ONLY 32x32.
    picboxAppLogo.Image = ExtractVistaIcon(icoAppIcon);
}

Note: I'm still using my ExtractVistaIcon() function here, because I don't like how IconExtractor handles this job - first, it extracts all icon formats by using IconExtractor.SplitIcon(icoAppIcon), and then you have to know the exact 256x256 icon index to get the desired vista-icon. So, using my ExtractVistaIcon() here is much faster and simplier way :)

Related Topic