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
Dim icoBinaryFile As New FileStream(filename, FileMode.Open,
FileAccess.Read)
Dim byteArray(icoBinaryFile.Length) As Byte
icoBinaryFile.Read(byteArray, 0, icoBinaryFile.Length)
icoBinaryFile.Close()
icoStream = New MemoryStream(byteArray)
icoStream.Seek(0, SeekOrigin.Begin)
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
Public Type As Short
Public Count As Short
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
Public Height As Byte
Public ColorCount As Byte
Public Reserved As Byte
Public Planes As Short
Public BitCount As Short
Public BytesInRes As Integer
Public ImageOffset As Integer
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)
Dim icoByteArray(thisIcon.BytesInRes) As Byte
Dim newIcon As New MemoryStream
Dim writer As New BinaryWriter(newIcon)
Dim newCount As Short = 1
Dim newOffset As Integer = 22
Console.WriteLine("Icon Index: " & index & ",
Offset: " & thisIcon.ImageOffset)
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)
icoStream.Seek(thisIcon.ImageOffset, SeekOrigin.Begin)
icoStream.Read(icoByteArray, 0, thisIcon.BytesInRes)
.Write(icoByteArray)
.Flush()
Console.WriteLine("Image written: " & newIcon.Position)
End With
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:
Dim newCount As Short = 1
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.