A while ago I finally completed the dynamic generation of favicons for my blog system. As we all know, favicon must have an icon in .ico format, and the rest can be output as png files and a manifest.json. However, this ICO format let me struggled for a while. I'd like to share my solution.

The Problem with Built-in API

As many of you may know, System.Drawing.Image has a Save method that allows us to pass in the image format so that we can get a particular format of an image file.

public void Save(string filename, ImageFormat format);
public sealed class ImageFormat
{
    public ImageFormat(Guid guid);
    public static ImageFormat Bmp { get; }
    public static ImageFormat Emf { get; }
    public static ImageFormat Exif { get; }
    public static ImageFormat Gif { get; }
    public static ImageFormat Icon { get; }
    public static ImageFormat Jpeg { get; }
    public static ImageFormat MemoryBmp { get; }
    public static ImageFormat Png { get; }
    public static ImageFormat Tiff { get; }
    public static ImageFormat Wmf { get; }
    public Guid Guid { get; }

    public override bool Equals(object o);
    public override int GetHashCode();
    public override string ToString();

}

So, when an image becomes a Bitmap object, you can call this method to save it in various formats. Although it may seem that ICON is also supported, if you do believe it, then the saved ICO is actually a PNG file, which can be opened with a HEX editor to see the file header information:

The Solution

In fact, .NET doesn't have a built-in ICO encoder, so that we can only implement the encoder ourselves. Luckily, there is a piece of code from 2004 that I found that be ported to .NET Core. It was written by Joshua Flanagan. I made it work with .NET Core and open-sourced with other parts of my blog system, you can find it here:

https://github.com/EdiWang/Moonglade/tree/master/src/Moonglade.Web.FaviconGenerator/IconEncoder

Usage sample:

private static void GenerateStandardFaviconIco(string originImagePath, string icoFilePath)
{
    var fs = new FileStream(originImagePath, FileMode.Open, FileAccess.Read);
    using (fs)
    {
        using var image = new Bitmap(fs);
        var ico = Converter.BitmapToIcon(image);
        using var icoFs = new FileStream(icoFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
        ico.Save(icoFs);
        icoFs.Flush();
    }
}

Now, you can get a true ICO format image file.

Caveats: The code is not guaranteed to generate the most size efficient icon files, and it contains certain hard-code values like the 24-bit color depth that may not work with a few images. You may still need to change some code based on your scenarios.