Click here to Skip to main content
Email Password   helpLost your password?

Introduction



GDI+ is really great for many things but there are a few missing pieces. One of those missing pieces is the ability to view the different versions of an icon contained in a single ICO file. In this article I will show you how to access and utilize these different versions.

Because everyone likes this topic I have written the code in C# as well.  I figured I'd dust off my C# skills and recode the app.  This new C# version is pretty much the same as the VB version except it exposes more details inside the icon file; see the source code for details.

Icon Files

Very often, icon files contain multiple versions of the same image. Usually these files will contain small and large versions of the same image so you will not have to resize the image and risk loosing valuable resolution.

Limitations of "new Icon"

The new icon constructor in Visual Studio does not have good support for multiple image versions. While it is true that you can specify the height and width of the icon you are requesting, you have no way of knowing what to request (if you don't know what's in the file). To get around this limitation we have to look at the ico file directly and pull out the bits and bytes that we want to utilize. I have created a graph that will illustrate the internal layout of an icon file.

For reasons of space I only put two icons in the above sample but there can be as many as you want.

Icon Header
Icon Header holds the key to accessing the entire file. This six Byte block tells you how many icons are in the file you are accessing as well as the type of file you are accessing (0 for Bitmap and 1 for Icon).

Icon Entry
After we read the Icon Header we will know how many icons are in this icon file. We can then read that many Icon Entry blocks safely. Icon Entry blocks do not hold the image information, they hold the Offset and the Length of the image Bytes.

Opening the icon file

The easiest way is to create a FileStream and dump the file to a Byte array. You can then load the Byte array into a Stream object. The following code will demonstrate.

    Private Function readIcoFile(ByVal filename As String) As MemoryStream
        ' Open the file

        '

        Dim icoBinaryFile As New FileStream(filename, FileMode.Open, 
                                            FileAccess.Read)


        ' Create the byte array and read it in

        '

        Dim byteArray(icoBinaryFile.Length) As Byte
        icoBinaryFile.Read(byteArray, 0, icoBinaryFile.Length)
        icoBinaryFile.Close()


        ' Load the stream with the bytearray

        '

        icoStream = New MemoryStream(byteArray)
        icoStream.Seek(0, SeekOrigin.Begin)


        ' Debug, these values should be the same!

        '

        Console.WriteLine("Number of bytes: " & byteArray.Length)
        Console.WriteLine("Length of Stream: " & icoStream.Length)
    End Function

Reading the data

The best way I have found to do this is create classes. The first class would be the icon header class and it would contain only definitions for the elements it reads and a new procedure that will read the information from the stream.

    Private Class iconHeader
        Public Reserved As Short      ' Always 0

        Public Type As Short          ' 0=Bitmap, 1=Icon

        Public Count As Short         ' Number of icons



        '------------------------------------------

        '     Sub: New

        ' Purpose: Read the six byte header from

        '          the raw ICO file

        '    Note: Short or Int16 are 2 bytes each

        '

        Public Sub New()
            Dim icoFile As New BinaryReader(icoStream)

            Reserved = icoFile.ReadInt16
            Type = icoFile.ReadInt16
            Count = icoFile.ReadInt16
        End Sub
    End Class

The next step is pretty much the same, but with the Icon Entry data.

    Private Class iconEntry
        Public Width As Byte          ' Width, in pixels, of the image

        Public Height As Byte         ' Height, in pixels, of the image

        Public ColorCount As Byte     ' Number of colors in image(0 if >=8bpp)

        Public Reserved As Byte       ' Reserved ( must be 0)

        Public Planes As Short        ' Color Planes

        Public BitCount As Short      ' Bits per pixel

        Public BytesInRes As Integer  ' How many bytes in this resource?

        Public ImageOffset As Integer ' Where in the file is this image?


        '------------------------------------------

        '     Sub: New

        ' Purpose: Read the sixteen byte header from

        '          the raw ICO file

        '    Note: Byte is 1 byte

        '          Short or Int16 are 2 bytes

        '          Integer or Int32 are 4 bytes

        '

        Public Sub New(ByVal Index As Integer)
            Dim icoFile As New BinaryReader(icoStream)

            Width = icoFile.ReadByte
            Height = icoFile.ReadByte
            ColorCount = icoFile.ReadByte
            Reserved = icoFile.ReadByte
            Planes = icoFile.ReadInt16
            BitCount = icoFile.ReadInt16
            BytesInRes = icoFile.ReadInt32
            ImageOffset = icoFile.ReadInt32
        End Sub
    End Class

You now have all the Offsets, Sizes, Color Information and Lengths of the images.

Building an image

Ok, what we do here is actually build an icon using the information we extracted from the headers and entries. We do this by creating another stream and using a BinaryWriter to fill in the data. Here is an example:

    Private Function buildIcon(ByVal index As Integer) As Icon
        Dim thisIcon As iconEntry = icons(index)

        ' Allocate the space for the icons byteArray

        Dim icoByteArray(thisIcon.BytesInRes) As Byte

        ' Create the stream

        Dim newIcon As New MemoryStream
        Dim writer As New BinaryWriter(newIcon)

        ' Only one icon in this file

        Dim newCount As Short = 1


        ' Six Bytes + Sixteen Bytes is the new offset

        Dim newOffset As Integer = 22

        Console.WriteLine("Icon Index: " & index & ", 
                          Offset: " & thisIcon.ImageOffset)

        ' Write the file

        With writer
            .Write(icoHeader.Reserved)
            .Write(icoHeader.Type)
            .Write(newCount)
            Console.WriteLine("Header written: " & newIcon.Position)

            .Write(thisIcon.Width)
            .Write(thisIcon.Height)
            .Write(thisIcon.ColorCount)
            .Write(thisIcon.Reserved)
            .Write(thisIcon.Planes)
            .Write(thisIcon.BitCount)
            .Write(thisIcon.BytesInRes)
            .Write(newOffset)
            Console.WriteLine("Image Header written: " & newIcon.Position)

            ' Read the icon from the stream

            icoStream.Seek(thisIcon.ImageOffset, SeekOrigin.Begin)
            icoStream.Read(icoByteArray, 0, thisIcon.BytesInRes)

            ' Write it out

            .Write(icoByteArray)
            .Flush()
            Console.WriteLine("Image written: " & newIcon.Position)
        End With

        ' Move to the start

        newIcon.Seek(0, SeekOrigin.Begin)

        Dim thisImage As Icon = New Icon(newIcon, thisIcon.Width, 
                                         thisIcon.Height)
        writer.Close()

        Return thisImage
    End Function

Notice, we are just writing back pretty much a copy of the data we read (with a few changes). The changes we made where:

' We only have one icon in this file 

Dim newCount As Short = 1 
' We are moving the image offset so it falls right after the headers 

Dim newOffset As Integer = 22 

The class to wrap all this

I created a class that I call MultiIcon it contains the following properties and methods:

Examples

Public thisIcon As multiIcon(filename)

This will create a new instance of the class.

PictureBox1.Image = thisIcon.image(ComboBox1.SelectedIndex).ToBitmap

This will load a icon into a picture box.

PictureBox1.Image = thisIcon.findIcon(
                                    multiIcon.iconCriteria.Largest).ToBitmap()
PictureBox1.Image = thisIcon.findIcon(
                                   multiIcon.iconCriteria.Smallest).ToBitmap()

This will load a specific version of an icon into a picturebox.

        Dim size As Size
        For Each size In thisIcon.sizes
            ComboBox1.Items.Add(size.ToString)
        Next

This will list all the sizes of the icons currently available.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalhow do i load an icon from a remote location such as a favicon.ico - http://www.codeproject.com/favicon.ico
ernster
1:33 26 Jun '09  
I've been trying to get a fav icon from website and get the icons by index to convert to pngs.

I tried to use a System.Net.HttpWebRequest and WebClient.OpenRead but if try and do new Icon(stream) it says the stream doesn't support seeking

if I use Image.FromStream(stream) it will work but doesn't retrive the icon and only returns the first image

thanks.
GeneralRe: how do i load an icon from a remote location such as a favicon.ico - http://www.codeproject.com/favicon.ico
Eli Gazit
20:25 19 Jul '09  
public static Image GetFavIcon(string url)
{
if (string.IsNullOrEmpty(url))
return null;
// Gets the fav icon, if it wasn't found before:
System.Net.WebClient wc = new System.Net.WebClient();
try
{
string strUrl = ExtractDomain(url) + "/favicon.ico";
Stream strm = wc.OpenRead(strUrl);

Bitmap objBitmap = new Bitmap(strm);
strm.Close();
// Save the bitmap, and reload it, to convert it to bmp:
string fileName = System.IO.Path.GetTempFileName();
objBitmap.Save(fileName);
objBitmap = (Bitmap)Bitmap.FromFile(fileName);
objBitmap.MakeTransparent();

//resize bitmap to 16X16

Bitmap result = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage((Image)result))
g.DrawImage(objBitmap, 0, 0, 16, 16);
retryn result;
}
catch (Exception)
{
// Eat exception - just don't show the image

return null;
}

}


private static string ExtractDomain(string url)
{
return ExtractDomain(url, true);
}

private static string ExtractDomain(string url, bool addWWW)
{
if (!url.Contains("://"))
url = addWWW ? "http://www." + url : "http://" + url;
return "http://" + new Uri(url).Host;
}
General256x256 PNG icon support? [modified]
smashly66
17:07 17 Jul '08  
Thank you for sharing. Smile
This code has the same flaws as my own code had. Sigh
Before reading this I had written an ico header reader writer for extracting single or multi ico from an ico or module (exe, dll, ocx, cpl) to a new ico file.

But 256x256 png ico throws a wobbly.
By reading the header of the ico you'll get 0x0 instead of 256x256
So I now add a check for bytes of the header for the w x h = 0x0, if so then treat it as 256x256.

Similar also applies to any other ico containing png of any w x h,
The w x h for a png in an ico, eg; 32x32 the header would report 32 x 64 ..
So once again I throw in a check that the w x h bytes are equal. If not then we check if the ico is png...

I can display the png data quite easily, but exporting png to an ico file and windows doesn't see it as an icon.

Mind you extract the icon group from a resource and the ico works fine, it's only when I try to single extract a png only ico that the code fails even though the header data all looks correct..
I'm still missing something when it comes to png in an ico.

Cheers

Edit: Seems like my own code does output png ico 256x256 data correctly.
Doh.. XP doesn't handle 256x256 compressed ico images..lol
That'll teach me not to check things in vista!

modified on Thursday, July 17, 2008 11:37 PM

GeneralHow to properly scale a System.Drawing.Icon
logan1337
16:28 24 Oct '07  
Just for the benefit of others, I've been scratching my head over this for a long time so I want to emphasize this:

If you want to access a particular size of icon embedded within an Icon object, use the new Icon constructor! This doesn't seem natural at all, but here's how you get the 16x16 variant of an icon from an icon object (e.g. application resource) that contains multiple versions:

Icon smallIcon = new Icon( Resources.originalIcon, new Size( 16, 16 ) );

If you just draw the icon to a 16x16 bitmap it won't select the proper size first and will draw a crappy scaled version.

(I came to this article looking for the answer to this question so I thought I'd post it rather than write a whole article for it. This comment doesn't actually have anything to do with Matthew's article.)

{o,o}.oO( Did somebody say “mouse”? )
|)””’)
-”-”-

GeneralRe: How to properly scale a System.Drawing.Icon
Matthew Hazlett
20:34 24 Oct '07  
If I recall correctly new icon, 16, 16 didn't yield a true 16x16 icon... It scaled the larger icon down to 16x16 loosing most of the icons detail and definition. (This gets worse the further you stray).





Matthew Hazlett
Fighting the good fight for web usability.

GeneralRe: How to properly scale a System.Drawing.Icon
logan1337
4:50 25 Oct '07  
It works for me (.NET 2.0) and is the ONLY way I've yet found to accomplish this. I'm glad too because it was really bugging me that I couldn't draw the proper icons. Smile

{o,o}.oO( Did somebody say “mouse”? )
|)””’)
-”-”-

QuestionHow-to use same to extract from a file/exe?
handheldmaster
10:50 2 Feb '07  
Need to do the same but not from ICON class but the EXE. Example if file is a word file then from the exe. Basically trying to build something similar to the windows explorer.
Generalcan I use the loaded icon as a windows icon handle
srinivas vaithianathan
15:34 23 Jan '06  
In VC++, if I follow your method and load the icons into memory (Globally allocated) and then I can I pass the memory handle as HICON?
GeneralRe: can I use the loaded icon as a windows icon handle
Matthew Hazlett
15:39 23 Jan '06  

Sorry, don't know Visual C++.

But I would think (and I may be totally wrong) that you might be able to pass the memory location as some sort of pointer?



Matthew Hazlett

GeneralRe: can I use the loaded icon as a windows icon handle
srinivas vaithianathan
8:36 24 Jan '06  
But thanks a lot for the article. It was very useful for me because there is no windows API at present to load an icon by index at present. Instead of memory handle, I write the icons extracted to a file and load it by windows API. It seems to work fine.
GeneralRe: can I use the loaded icon as a windows icon handle
Querulant
2:11 15 Aug '06  
With this code you get an Array(List) of Icon Objects. I haven't tried it out, but I think you can easily access to the handle. Note, that the .NET Framework can move the handle "around". This would be fatal, if you would like to use the handle in unmanaged code. To preserve it, try the following code:

//
using System.Runtime.InteropServices
//
GCHandle hPinnedIco = GCHandle.Alloc(icon, GCHandleType.Pinned);
IntPtr hIcon = hPinnedIco.AddrOfPinnedObject();
// The Pointer to the Icon is now "pinned" (fixed in memory)
// Do something with hIcon and don't forget to free the Handle:
if( hPinnedIco.IsAllocated )
hPinnedIco.Free();

Hope, this would help.
Greetings, Peter

GeneralConvert PNG to ICO
jgallen23
18:33 1 Nov '05  
How do I go about Converting a PNG file to an ICO file with various sizes?

All I really need to find out is how do I get:
ColorCount = icoFile.ReadByte();
Reserved = icoFile.ReadByte();
Planes = icoFile.ReadInt16();
BitCount = icoFile.ReadInt16();
BytesInRes = icoFile.ReadInt32();

from the png

Thanks for the great article!

JGA
GeneralMore than one icon
TsahiM
9:51 28 Jun '04  
Is it possible to create an System.Drawing.Icon object with more than size in it using MultiIcon class?

Thanks

Tsahi
GeneralAccessing icon libraries
mikestu
3:56 2 Mar '04  
What about reading/writing to an icon library file (a .dll file that contains multiple icons)? I have done this in the past using COM, but I haven't seen a native .net version. Great work! Smile
GeneralRe: Accessing icon libraries
João Rezende
11:17 14 Apr '05  
I agree..!Blush
GeneralRe: Accessing icon libraries
Unruled Boy
17:22 10 Jul '05  
icon library format : .ICL ?

Regards,
unruledboy@hotmail.com


Last Updated 24 Feb 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010