ICO files are old but still widely used. One unique aspect of the ICO format is that it can contain multiple images of different sizes. The format itself is straightforward, yet free tools to create an ICO file from a list of images are scarce. Let's build one in .NET!
The ICO format specification is available on Wikipedia. The code below is a simple implementation of the specification. It supports PNG images only and does not cover all features of the format.
Create a console application and add the ImageSharp package. The ImageSharp library handles converting images to PNG and reading their dimensions.
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!