返回文章列表文章索引阅读全文
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 许可协议。转载请注明出处!

相关文章

讨论

共 0 条

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

加载评论中…

Took quite some time to fix a bug in SharpIco's icon generation. · StarBlog