曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
C#

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?

曦远 (DealiAxy)约 11 分钟
目录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".

image-20250701231131284

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#学习(三)深入理解委托、匿名方法和-Lambda-表达式

前言 lambda的概念建立在委托的基础上,委托,实现了类型安全的回调方法。在.NET 中回调无处不在,所以委托也无处不在,事件模型建立在委托机制上,Lambda 表达式本质上就是一种匿名委托。本文中将完成一次关于委托的旅行,全面阐述委托及其核心话题,逐一梳理委托、委托链、事件、匿名方法和 Lambda 表达式。 委托的定义 了解委托,从其定义开始,通常一个委托被声明为: public deleg

2021-12-18 · C# · 约 1 分钟

C#学习(二)匿名方法和委托的多种使用方式

前言 上次简单介绍了委托的概念和使用,今天继续,并且引入匿名方法的概念,匿名方法在很多语言里面都有,比如Python、js、java8,尤其js,前端小伙伴可能深受js回调地狱之苦,这里面匿名方法功不可没。 js的匿名方法就像这样: 这还没用lambda,因为我想下篇再介绍lambda。 document.querySelector('.btn').onclick = function () {

2021-12-18 · C# · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…