Took quite some time to fix a bug in SharpIco's icon generation.
SharpIco bug fixed? You really think you're something, but it crashes when converting high-res images to ico. That logic is wild. Turns out it was a bit depth issue—just 24 to 32 bits—why not use some real skills to solve it?
目录5 个章节
Preface
Last month, I developed an ICO icon generator called SharpIco using dotnet9 AOT.
This practical little tool gained quite a bit of attention as soon as it was released.
Recently, it has even been made into a website with a graphical interface for one-click ICO icon generation, making it more convenient for ordinary users.
Problem Occurrence
However, some users reported issues in the comments stating that "high-resolution images become corrupted and can't be opened after conversion".

I tested it and indeed encountered the same problem.
This is quite strange.
Looking at the code, it's clear that images of each size are generated through Resize (调整大小).
This means that regardless of the input image's original size, it will ultimately be scaled down to a few predefined sizes.
using var original = Image.Load(sourcePng);
foreach (var size in sizes) {
using var clone = original.Clone(ctx => ctx.Resize(size, size));
using var ms = new MemoryStream();
clone.SaveAsPng(ms);
images.Add(ms.ToArray());
}
Moreover, the generated .ico file is not truly corrupted—it can still be opened in Photoshop. However, it cannot be viewed in the system's image viewer, and the image metadata cannot be parsed correctly.
Troubleshooting Issues
Initially, I sought help from Grandpa LLM (Claude4)
But none of the solutions it gave me were useful
Either it told me to use BMP format for resolutions below 256
Then I'd have to write some messy BIP header or whatever, a real hassle
In the end, I had to observe carefully myself
The code for writing each image looks like this:
foreach (var image in images) {
using var ms = new MemoryStream(image);
using var img = Image.Load(ms);
writer.Write((byte)(img.Width == 256 ? 0 : img.Width)); // width
writer.Write((byte)(img.Height == 256 ? 0 : img.Height)); // height
writer.Write((byte)0); // colors in palette
writer.Write((byte)0); // reserved
writer.Write((ushort)1); // color planes
writer.Write((ushort)32); // bits per pixel
writer.Write(image.Length); // size of image data
writer.Write(offset); // offset of image data
offset += image.Length;
}
There is a line writer.Write((ushort)32); // bits per pixel which represents the bit depth of the image's color.
Generally, the common bit depths supported by ICO are:
- 1 bit (black and white)
- 4 bit (16 colors)
- 8 bit (256 colors)
- 24 bit (true color)
- 32 bit (true color + Alpha)
Our code directly uses 32-bit color here.
And I found that the images that couldn't be parsed after conversion were all 24-bit color.
So that's the mystery solved.
It's a bit depth mismatch issue.
Solution
So how can we solve this?
At first, I tried adding a transparent background color when cloning images of different sizes:
using var clone = original.Clone(ctx => {
ctx.Resize(size, size);
ctx.BackgroundColor(Color.Transparent); // Ensure there is an alpha channel
});
Unfortunately, this had no effect.
I suspect the issue is that if the original image lacks an alpha channel, ImageSharp still saves the PNG using RGB, resulting in a 24-bit PNG.
However, the ICO header is written as 32-bit, causing it to fail to parse.
The final solution was:
foreach (var size in sizes) {
using var clone = original.Clone(ctx => ctx.Resize(size, size));
using var ms = new MemoryStream();
// Force the image to Rgba32 format, ensuring it is 32-bit
var rgbaImage = clone.CloneAs<Rgba32>();
rgbaImage.SaveAsPng(ms);
images.Add(ms.ToArray());
}
By using clone.CloneAs<Rgba32>();, I forcefully converted the image to 32-bit color (32-bit color).
After this, I tested most images, and they all worked fine ~
Summary
I thought that just being familiar with the ICO file format specification would be enough.
Little did I know this was just the beginning.
There are still many pitfalls in the field of image processing.
It’s no wonder that most tools choose to implement this functionality using mature, long-established image processing libraries like Magick.
When writing it by hand, it’s easy to stumble into problems. And it takes a lot of time to debug and troubleshoot them!
_EOF_
Took quite some time to fix a bug in SharpIco's icon generation.
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
让 C# 也拥有 gofmt 般的体验:用 CSharpier + Roslyn 实现 csfmt
C# 代码长到像答辩?用 CSharpier 自动折行,再用 Roslyn 修正 K&R 花括号,Taskfile 一键收尾。再补上 format-check,省得 CI 反复制造 Git diff。两步流水线并非原生 gofmt,顺序和版本仍得老实锁住。
2026-10-22 · C# · 约 1 分钟
纯 C#实现+AOT 打造的多功能图片处理工具 ImageGlider
纯C#+AOT打造的ImageGlider,图像处理功能堆得像山,还非得标榜零依赖?ImageSharp封装得倒是挺顺溜,就是不知道这模块化架构能走多远。Web API和CLI工具有点东西,但别指望我夸太离谱。
2025-08-05 · C# · 约 1 分钟
C#学习(二)匿名方法和委托的多种使用方式
前言 上次简单介绍了委托的概念和使用,今天继续,并且引入匿名方法的概念,匿名方法在很多语言里面都有,比如Python、js、java8,尤其js,前端小伙伴可能深受js回调地狱之苦,这里面匿名方法功不可没。 js的匿名方法就像这样: 这还没用lambda,因为我想下篇再介绍lambda。 document.querySelector('.btn').onclick = function () {
2021-12-18 · C# · 约 1 分钟
开发现代化的.NetCore控制台程序:(1)项目介绍以及依赖注入/配置/日志等使用细节
开发.NET Core控制台工具,依赖注入和日志整合得比Go优雅多了。哼,配置文件丝滑,但AOT?反射问题,放弃。
2023-11-10 · C# · 约 1 分钟
讨论
共 0 条加载评论中…