Creating ico files from multiple images in .NET

 
 
  • Gérald Barré

Ico files are old but still widely used. One specificity of the ico format is that it can contain multiple images of different sizes. The format is not very complicated but there are not a lot of free tools to create an ico file from a list of images. Let's create one in .NET!

The specification of the ico format is available on Wikipedia. The following code is a simple implementation of the specification. It will only support PNG images and will not support all the features of the format.

Let's create a console application and add the ImageSharp. The ImageSharp library will be used to convert the images to PNG and also to read the dimensions of the images.

Shell
dotnet new console
dotnet add package SixLabors.ImageSharp
C#
using SixLabors.ImageSharp;

string[] inputFiles = [ "img_1.png", "img_2.png" ];
string outputFile = "output.ico";
await CreateIcon(inputFiles, outputFile);

// Spec https://en.wikipedia.org/wiki/ICO_(file_format)
static async Task CreateIcon(string[] inputFiles, string outputFile)
{
    // Convert images to png and extract image dimensions
    var pngs = new (byte[] Data, int Width, int Height)[inputFiles.Length];
    for (int i = 0; i < inputFiles.Length; i++)
    {
        using var image = await Image.LoadAsync(inputFiles[i]);
        var pngStream = new MemoryStream();
        image.SaveAsPng(pngStream);
        pngs[i] = (pngStream.ToArray(), image.Width, image.Height);
    }

    // Create the ico file
    await using var output = File.OpenWrite(outputFile);
    await using var iconWriter = new BinaryWriter(output);

    // Write header
    // 0-1 reserved
    iconWriter.Write((byte)0);
    iconWriter.Write((byte)0);

    // 2-3 image type, 1 = icon, 2 = cursor
    iconWriter.Write((short)1);

    // 4-5 number of images
    iconWriter.Write((short)inputFiles.Length);

    // image data offset
    // ico header (6 bytes) + image directory (16 bytes per image)
    long offset = 6 + (16 * inputFiles.Length);

    // Write image directory
    foreach (var png in pngs)
    {
        // Convert img to png
        var dataLength = png.Data.Length;

        // 0 image width
        iconWriter.Write((byte)(png.Width >= 256 ? 0 : png.Width));

        // 1 image height
        iconWriter.Write((byte)(png.Height >= 256 ? 0 : png.Height));

        // 2 number of colors, 0 if the image does not use a color palette
        iconWriter.Write((byte)0);

        // 3 reserved
        iconWriter.Write((byte)0);

        // 4-5 color planes
        iconWriter.Write((short)0);

        // 6-7 bits per pixel, png use 4 bytes per pixel (argb)
        iconWriter.Write((short)32);

        // 8-11 size of image data
        iconWriter.Write((uint)dataLength);

        // 12-15 offset of image data
        iconWriter.Write((uint)offset);

        offset += dataLength;
    }

    // Write image data
    // png data must contain the whole png data file
    foreach (var png in pngs)
    {
        iconWriter.Write(png.Data);
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub