ImageSharp
| Language | C# (.NET 8 or later) |
|---|---|
| License | Six Labors Split License: Apache 2.0 for open source projects and companies under $1M annual revenue, otherwise a commercial license |
| Repository | github.com/SixLabors/ImageSharp |
| Documentation | ICO in the ImageSharp docs |
| Depends on | nothing native (pure managed code) |
| Read / write | yes / yes (version 4.0 and later), BMP and PNG entries, CUR read and write with hotspots |
ImageSharp reads and writes ICO and CUR from version 4.0, in pure managed code. Every icon entry becomes one frame of the image. Because ImageSharp frames share one size, smaller entries sit in the top-left corner of a canvas as large as the biggest entry; IcoFrameMetadata.EncodingWidth and EncodingHeight give the real size of each entry, and the encoder crops to them when writing.
Per-frame metadata controls storage: Compression (IconFrameCompression.Bmp or Png) and BmpBitsPerPixel for BMP entries. CUR files use CurFrameMetadata, which adds HotspotX and HotspotY. Check the license before you use it in a commercial closed-source product.
Installation#
dotnet add package SixLabors.ImageSharp
Basic usage: list the entries of an ICO file#
Image.Identify reads the directory without decoding pixels:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Ico;
ImageInfo info = Image.Identify("app.ico");
for (int i = 0; i < info.FrameCount; i++)
{
IcoFrameMetadata m = info.FrameMetadataCollection[i].GetIcoMetadata();
int w = m.EncodingWidth is > 0 ? m.EncodingWidth.Value : 256; // 0 means 256
int h = m.EncodingHeight is > 0 ? m.EncodingHeight.Value : 256;
Console.WriteLine($"{i}: {w} x {h}, {m.Compression}, {m.BmpBitsPerPixel}");
}
How to export every icon size to PNG#
Clone each frame and crop it to the entry’s own size:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Image<Rgba32> icon = Image.Load<Rgba32>("app.ico");
for (int i = 0; i < icon.Frames.Count; i++)
{
IcoFrameMetadata m = icon.Frames[i].Metadata.GetIcoMetadata();
int w = m.EncodingWidth is > 0 ? m.EncodingWidth.Value : icon.Width;
int h = m.EncodingHeight is > 0 ? m.EncodingHeight.Value : icon.Height;
using Image<Rgba32> entry = icon.Frames.CloneFrame(i);
entry.Mutate(x => x.Crop(w, h));
entry.SaveAsPng($"icon-{w}x{h}.png");
}
How to convert a PNG to a multi-size ICO#
Add one frame per size. Each frame has the canvas size (256x256); the smaller image is drawn in the top-left corner and EncodingWidth/EncodingHeight tell the encoder how much to keep:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Image<Rgba32> icon = Image.Load<Rgba32>("logo.png");
icon.Mutate(x => x.Resize(256, 256));
icon.Frames.RootFrame.Metadata.GetIcoMetadata().Compression = IconFrameCompression.Png;
foreach (int size in new[] { 48, 32, 16 })
{
using Image<Rgba32> small = icon.Clone(x => x.Resize(size, size));
using Image<Rgba32> canvas = new(256, 256); // transparent
canvas.Mutate(x => x.DrawImage(small, new Point(0, 0), 1f));
ImageFrame<Rgba32> frame = icon.Frames.AddFrame(canvas.Frames.RootFrame);
IcoFrameMetadata m = frame.Metadata.GetIcoMetadata();
m.Compression = IconFrameCompression.Bmp;
m.EncodingWidth = (byte)size;
m.EncodingHeight = (byte)size;
}
icon.Save("app.ico", new IcoEncoder());
How to read a cursor hotspot#
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Cur;
using Image cursor = Image.Load("pointer.cur");
CurFrameMetadata m = cursor.Frames.RootFrame.Metadata.GetCurMetadata();
Console.WriteLine($"hotspot: {m.HotspotX}, {m.HotspotY}");
Set HotspotX and HotspotY on each frame and save with new CurEncoder() to write a cursor.
Tips#
- ImageSharp 3.x and older don’t have ICO:
Image.LoadthrowsUnknownImageFormatException. - Large entries stored as PNG are always 32-bit RGBA, as Windows expects.