Bitmap.Save保存图标实际上可以保存.png

我需要编写一个程序,根据tileset图像生成108个图标组合(标准窗口.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可以,如果我在Hex查看器中打开它,这就是我所看到的:

 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); 

确实, ImageFormat.Icon不能像你想象的那样工作,.NET根本不支持编写.ico文件并简单地转储PNG数据。

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

我自己做了一个快速和肮脏的解决方法,我在这里发布它的记录(这可能有助于需要快速解决方案的人,像我一样)。

我不会接受这个作为正确的答案,它不是一个真正的图标作家。 它只是使用PNG格式将32位ARGB位图写入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(); } 

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

 // https://en.wikipedia.org/wiki/ICO_(file_format) public static class IconWriter { public static void Write(Stream stream, IReadOnlyList 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)); } }