Access multiple icons in a single icon file

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 Bytearray into a Stream object. The following code will demonstrate.
CODES:
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.
CODES:
The next step is pretty much the same, but with the Icon Entry data.
CODES:
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:

CODES:







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

The class to wrap all this
I created a class that I call MultiIcon it contains the followin 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
CODES:

No comments:

Post a Comment