位图保存为图标实际上保存的是 .png 文件

3

我需要编写一个程序,基于一张图块集合的图片生成108种图标(标准的Windows .ico文件)。

我使用 System.Drawing.Bitmap 类来构建每个组合,并将它们保存如下:

Bitmap IconBitmap = new Bitmap(16, 16);
// Some processing, writing different parts of the source tileset
// ...
IconBitmap.Save(Path.Combine(TargetPath, "Icon" + Counter + ".ico"),
                ImageFormat.Icon);

但我发现保存的文件实际上是PNG格式。无论是Windows资源管理器还是Visual Studio都无法正确显示它,但GIMP可以,并且如果我在十六进制查看器中打开它,这就是我看到的内容:

00000000  89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52  ‰PNG........IHDR
00000010  00 00 00 10 00 00 00 10 08 06 00 00 00 1F F3 FF  ..............óÿ
00000020  61 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00  a....sRGB.®Î.é..
00000030  00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00  ..gAMA..±..üa...
00000040  00 09 70 48 59 73 00 00 0E C3 00 00 0E C3 01 C7  ..pHYs...Ã...Ã.Ç
00000050  6F A8 64 00 00 00 15 49 44 41 54 38 4F 63 60 18  o¨d....IDAT8Oc`.
00000060  05 A3 21 30 1A 02 A3 21 00 09 01 00 04 10 00 01  .£!0..£!........
00000070  72 A5 13 76 00 00 00 00 49 45 4E 44 AE 42 60 82  r¥.v....IEND®B`‚

如果我将.ico重命名为.png,Windows资源管理器可以正确显示它。

即使我对位图什么都不做(我使用“new”构建它并直接“Save”),也会得到黑色的png文件。

我做错了什么?

我还尝试过使用句柄来解决这个问题,但这给了我可怕的16色图标,我更愿意避免这种方法:

Icon NewIcon = Icon.FromHandle(IconBitmap.GetHicon());
FileStream FS = new FileStream(Path.Combine(Target, "Icon" + Counter + ".ico"),
        FileMode.Create);
NewIcon.Save(FS);

2
看起来你运气不好,根据Microsoft Support的内容。 - Steve
ICO是一种容器格式,可以接受PNG或位图编码的图像,因此您在那里看到的行为是正确的。也许你可以分享你的处理代码,这样我们就可以看看你是否做了任何使你的图标显示不正确的事情。此外,我认为只有从Vista开始才能显示PNG ico文件,所以如果你在XP上或需要XP兼容性,你可能会很倒霉。 - Chris Dworetzky
2
可能是重复的问题 https://dev59.com/LFHTa4cB1Zd3GeqPRnjt - Antonio Bakula
Steve:感谢提供链接,不幸的是这个 bug 是“按设计要求”的……很遗憾它甚至没有抛出异常。FurDworetzky:不,这不正确,我有一个实际的 PNG 作为结果,并且正如你指出的,ICO 是一个容器。在生成的文件中没有容器数据。AntonioBakula:看起来是这样,但我在我的第一次搜索中没有找到它…… - Benlitz
3个回答

3

这是我今天写的一个简单的ICO文件编写器,它可以将多个System.Drawing.Image图像输出到一个文件中。

// https://en.wikipedia.org/wiki/ICO_(file_format)

public static class IconWriter
{
    public static void Write(Stream stream, IReadOnlyList<Image> images)
    {
        if (images.Any(image => image.Width > 256 || image.Height > 256))
            throw new ArgumentException("Image cannot have height or width greater than 256px.", "images");

        //
        // ICONDIR structure
        //

        WriteInt16(stream, 0); // reserved
        WriteInt16(stream, 1); // image type (icon)
        WriteInt16(stream, (short) images.Count); // number of images

        var encodedImages = images.Select(image => new
        {
            image.Width,
            image.Height,
            Bytes = EncodeImagePng(image)
        }).ToList();

        //
        // ICONDIRENTRY structure
        //

        const int iconDirSize = 6;
        const int iconDirEntrySize = 16;

        var offset = iconDirSize + (images.Count*iconDirEntrySize);

        foreach (var image in encodedImages)
        {
            stream.WriteByte((byte) image.Width);
            stream.WriteByte((byte) image.Height);
            stream.WriteByte(0); // no pallete
            stream.WriteByte(0); // reserved
            WriteInt16(stream, 0); // no color planes
            WriteInt16(stream, 32); // 32 bpp

            // image data length
            WriteInt32(stream, image.Bytes.Length);

            // image data offset
            WriteInt32(stream, offset);

            offset += image.Bytes.Length;
        }

        //
        // Image data
        //

        foreach (var image in encodedImages)
            stream.Write(image.Bytes, 0, image.Bytes.Length);
    }

    private static byte[] EncodeImagePng(Image image)
    {
        var stream = new MemoryStream();
        image.Save(stream, ImageFormat.Png);
        return stream.ToArray();
    }

    private static void WriteInt16(Stream stream, short s)
    {
        stream.WriteByte((byte) s);
        stream.WriteByte((byte) (s >> 8));
    }

    private static void WriteInt32(Stream stream, int i)
    {
        stream.WriteByte((byte) i);
        stream.WriteByte((byte) (i >> 8));
        stream.WriteByte((byte) (i >> 16));
        stream.WriteByte((byte) (i >> 24));
    }
}

3
我自己制作了一个快速而简单的解决方法,并在此记录下来(它可能会帮助像我一样需要快速解决方案的人)。
我不会接受这个作为正确答案,因为它不是一个真正的图标编写器。 它只是将一个32位ARGB位图以PNG格式写入ico文件中(适用于Vista或更高版本)。
它基于维基百科上的ICO文件格式文章和一些失败和重试。
void SaveAsIcon(Bitmap SourceBitmap, string FilePath)
{
    FileStream FS = new FileStream(FilePath, FileMode.Create);
    // ICO header
    FS.WriteByte(0); FS.WriteByte(0);
    FS.WriteByte(1); FS.WriteByte(0);
    FS.WriteByte(1); FS.WriteByte(0);

    // Image size
    FS.WriteByte((byte)SourceBitmap.Width);
    FS.WriteByte((byte)SourceBitmap.Height);
    // Palette
    FS.WriteByte(0);
    // Reserved
    FS.WriteByte(0);
    // Number of color planes
    FS.WriteByte(0); FS.WriteByte(0);
    // Bits per pixel
    FS.WriteByte(32); FS.WriteByte(0);

    // Data size, will be written after the data
    FS.WriteByte(0);
    FS.WriteByte(0);
    FS.WriteByte(0);
    FS.WriteByte(0);

    // Offset to image data, fixed at 22
    FS.WriteByte(22);
    FS.WriteByte(0);
    FS.WriteByte(0);
    FS.WriteByte(0);

    // Writing actual data
    SourceBitmap.Save(FS, ImageFormat.Png);

    // Getting data length (file length minus header)
    long Len = FS.Length - 22;

    // Write it in the correct place
    FS.Seek(14, SeekOrigin.Begin);
    FS.WriteByte((byte)Len);
    FS.WriteByte((byte)(Len >> 8));

    FS.Close();
}

0

确实,ImageFormat.Icon 并不能像你想象的那样用于写入操作,.NET 简单地不支持写入 .ico 文件,并且只是转储 PNG 数据。

CodeProject(以及这个)(以及另一个)上有一些项目可以让你写入 .ico 文件,实际上并不难。文件格式非常直观,支持 BMP 和 PNG 数据。


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