找回密码
 立即注册
首页 业界区 业界 花了不少时间,修复了一个SharpIco生成图标的bug ...

花了不少时间,修复了一个SharpIco生成图标的bug

撷监芝 昨天 22:07
前言

上个月我用 dotnet9 AOT 开发了一个 ico 图标生成工具 SharpIco
这个实用小工具一经发布就受到不少朋友的关注
最近还被做成了网站,有图形化界面来一键生成 ico 图标,更方便普通用户的使用
出现问题

不过有网友在 issues 反馈了问题 「高分辨率图片在转换完成后,图片会损坏导致无法打开」
1.png

我测试了一下确实会这样
这个就很诡异了
看代码也可以发现,每个尺寸的图片都是通过 Resize 出来的
这样的话,无论输入的图片是多大的,最终都会缩小成几个指定的尺寸
  1. using var original = Image.Load(sourcePng);
  2. foreach (var size in sizes) {
  3.   using var clone = original.Clone(ctx => ctx.Resize(size, size));
  4.   using var ms = new MemoryStream();
  5.   clone.SaveAsPng(ms);
  6.   images.Add(ms.ToArray());
  7. }
复制代码
而且生成的 ico 并不是真正的损坏,在 Photoshop 里还是能打开的,只不过在系统的图片应用打开看不了,而且也无法正确解析图片元数据
排查问题

起初我求助于大模型爷爷(Claude4)
不过给我的几个方案都没啥用
要不就是叫我低于 256 分辨率使用 BMP 保存
然后还得写入 BIP 头什么的乱七八糟的,麻烦得一批
最后还得是我自己仔细观察
写入每张图片的代码是这样的
  1. foreach (var image in images) {
  2.   using var ms = new MemoryStream(image);
  3.   using var img = Image.Load(ms);
  4.   writer.Write((byte)(img.Width == 256 ? 0 : img.Width)); // width
  5.   writer.Write((byte)(img.Height == 256 ? 0 : img.Height)); // height
  6.   writer.Write((byte)0); // colors in palette
  7.   writer.Write((byte)0); // reserved
  8.   writer.Write((ushort)1); // color planes
  9.   writer.Write((ushort)32); // bits per pixel
  10.   writer.Write(image.Length); // size of image data
  11.   writer.Write(offset); // offset of image data
  12.   offset += image.Length;
  13. }
复制代码
其中有一行  writer.Write((ushort)32); // bits per pixel 这个代表了图片的颜色位深度
一般来说 ICO 支持的常见位深度有:

  • 1 bit(黑白)
  • 4 bit(16 色)
  • 8 bit(256 色)
  • 24 bit(真彩色)
  • 32 bit(真彩色 + Alpha)
我们的代码这里直接使用了 32 位色彩
而我发现那些转换出来无法解析的图片,都是 24 位色彩的
那么到这里就破案了
就是位深度不匹配的问题
解决方法

那么如何解决呢?
一开始我尝试在 clone 不同大小的图片时加上透明背景色
  1. using var clone = original.Clone(ctx => {
  2.   ctx.Resize(size, size);
  3.   ctx.BackgroundColor(Color.Transparent); // 保证有 alpha 通道
  4. });
复制代码
然后很遗憾的是,没有效果
感觉可能是原始图片没有 alpha,ImageSharp 仍然用 RGB 来保存 PNG,导致保存出的 PNG 是 24-bit
而 ICO 写的 header 是 32-bit,导致无法解析
最终的解决方法是
  1. foreach (var size in sizes) {
  2.   using var clone = original.Clone(ctx => ctx.Resize(size, size));
  3.   using var ms = new MemoryStream();
  4.   // 强制图像为 Rgba32 格式,确保是 32-bit
  5.   var rgbaImage = clone.CloneAs<Rgba32>();
  6.   rgbaImage.SaveAsPng(ms);
  7.   images.Add(ms.ToArray());
  8. }
复制代码
使用 clone.CloneAs(); 强制转换图片位 32 位色彩
之后我测试了大部分图片都没问题了~
小结

我以为只要熟悉了ico文件格式的规范就足够了
没想到这只是个开始
图像处理领域还是有很多坑的
难怪大部分工具都选择使用 Magick 这类老牌成熟的图像处理库来实现这个功能
手写的话一不小心就踩坑了~ 还要花大量时间去排查问题

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册