Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Access multiple icons in a single icon file

0.00/5 (No votes)
23 Feb 2004 1  
A class to help you access the images in an ICO file (VB & C#)

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:

  • count
    The number of icons in an ico file
  • sizes
    An array of Size with the sizes of the icons in the ICO
  • image
    Returns an image for a specific icon
  • findIcon
    Returns an image but you can specify if you want the largest or smallest image

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here