如何将图像转换为图标而不丢失透明度?

14

我有一些PNG图片需要在显示之前转换为图标。

这是我的做法:

public Icon ImageToIcon(Image imgTest)
{
    Bitmap bitmap = new Bitmap(imgTest);
    Icon icoTest;

    IntPtr iPtr = bitmap.GetHicon();
    icoTest = (Icon) Icon.FromHandle(iPtr).Clone();

    return icoTest;
}

我做了这个之后失去了透明度,透明的 alpha 图像没有按预期呈现...这个问题能解决吗?


我最近将我的绘图库开源了,其中包含一个ToIcon扩展方法。或者使用Icons.Combine方法创建多图标。 - György Kőszeg
1个回答

31

不,这里面还有很多要讲的。图标具有相当复杂的内部结构,经过优化,可以在1980年代的硬件上正常运行。一个图标图像有三个位图,一个用于图标本身,一个单色位图用于指示图像中哪些部分是透明的,另一个单色位图用于指示哪些部分是翻转的。生成这些单色位图非常痛苦,.NET不支持它们。Bitmap.GetHicon()也没有尝试进行此操作。你需要一个库来为你完成这项工作。

Vista提供了一些缓解,开始支持包含PNG图像的图标。你可以尝试使用自己的代码生成它,就像这样:

    public static Icon IconFromImage(Image img) {
        var ms = new System.IO.MemoryStream();
        var bw = new System.IO.BinaryWriter(ms);
        // Header
        bw.Write((short)0);   // 0 : reserved
        bw.Write((short)1);   // 2 : 1=ico, 2=cur
        bw.Write((short)1);   // 4 : number of images
        // Image directory
        var w = img.Width;
        if (w >= 256) w = 0;
        bw.Write((byte)w);    // 0 : width of image
        var h = img.Height;
        if (h >= 256) h = 0;
        bw.Write((byte)h);    // 1 : height of image
        bw.Write((byte)0);    // 2 : number of colors in palette
        bw.Write((byte)0);    // 3 : reserved
        bw.Write((short)0);   // 4 : number of color planes
        bw.Write((short)0);   // 6 : bits per pixel
        var sizeHere = ms.Position;
        bw.Write((int)0);     // 8 : image size
        var start = (int)ms.Position + 4;
        bw.Write(start);      // 12: offset of image data
        // Image data
        img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
        var imageSize = (int)ms.Position - start;
        ms.Seek(sizeHere, System.IO.SeekOrigin.Begin);
        bw.Write(imageSize);
        ms.Seek(0, System.IO.SeekOrigin.Begin);

        // And load it
        return new Icon(ms);
    }

在.NET 4.5和Windows 8.1上测试过。请注意,当PNG图像具有透明度边缘时,您可能会看到“边缘”。只有在图像显示在众所周知的背景颜色上时,才能正常工作。而根据设计,图标永远不能依赖于这一点。专用图标编辑器始终是获取外观良好的图标的唯一真正好方法。


对于一个“光标”,将类型设置为2,并写入热点X和Y坐标,而不是颜色平面计数和BPP。 - Eugene Pankov
这个功能非常好用(仍然在.NET 7上)。需要注意的是,MemoryStream和BinaryWriter都应该调用Dispose,否则它们会泄漏。将它们都包裹在using语句中,它们将在返回后被处理掉。 - undefined
这只是内存,已经由垃圾回收器管理了。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接