以编程方式创建高质量的ico文件

时间:2013-01-04 12:21:23

标签: c# .net-4.0

我正在尝试在C#中以编程方式从PNG文件创建高质量图标(表示:suitable for Win Vista/7/8),以用作快捷方式图标。由于Bitmap.GetHIcon()函数不支持这些图标,我想避免外部依赖或库,我现在使用的是一个稍微修改过的ICO编写器,我发现here on SO。 我有工作代码,但我遇到了Windows显示这些图标的方式的一些故障。 相关代码是:

// ImageFile contains the path to PNG file
public static String IcoFromImageFile(String ImageFile) {
    //...       
    Image iconfile = Image.FromFile(ImageFile);

    //Returns a correctly resized Bitmap        
    Bitmap bm = ResizeImage(256,256,iconfile);                
    SaveAsIcon(bm, NewIconFile);

    return NewIconFile;

}        

// From: https://stackoverflow.com/a/11448060/368354
public static 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
    // Set to 0 for 256 px width/height
    FS.WriteByte(0);
    FS.WriteByte(0);
    // Palette
    FS.WriteByte(0);
    // Reserved
    FS.WriteByte(0);
    // Number of color planes
    FS.WriteByte(1); 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, System.Drawing.Imaging.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();
}

这个编译和工作,但有一个问题。 Windows错误地显示快捷方式上的图标。我也是以编程方式执行此操作,但即使我手动执行此操作也会发生(通过“文件属性”,“更改图标”)。问题是图标被切断(图像本身正确显示)。它取决于图像,但通常只显示实际图标的20%左右。如果我在像XNView这样的图像查看器中打开文件,它会完全正确显示,但MS Paint不会。 我制作了这个截图,以及一个正确显示的图标进行比较

enter image description here

我怀疑错误在于ICO保存方法,但即使将它们与Hex编辑器中正常显示的ICO进行比较,标题也会正确写入,但PNG图像部分本身似乎有所不同。有人有想法吗?我也欢迎更好,更少hacky的解决方案。

1 个答案:

答案 0 :(得分:7)

您的ico文件设置为仅以16位精度保存嵌入式位图的长度,但PNG文件太大(大于65535字节),因此长度记录溢出。

即。以下几行不完整:

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

您可以添加以下行:

FS.WriteByte((byte)(Len >> 16));
FS.WriteByte((byte)(Len >> 24));

作为清洁和性能的问题,我通常会避免所有这些单独的写入,并且只使用字节数组参数的写入重载。此外,您可以考虑一个Save-To-MemoryStream,然后是一个Write的头部(现在可以使用PNG的字节长度)和一次写入来复制PNG,而不是稍微棘手的Save-To-File。从内存流到文件的数据。

您真正应该解决的另一点是处理IDisposable资源。即使你还没有遇到任何问题,你也不需要它,有一天它会咬你,如果你有一个相当小的代码库和所有类型的无用的一次性用品,你将很难找到源的你的泄漏和/或死锁。一般情况下:从不调用Close,除非你真的无法避免 - 而是将FileStream包裹在using块中。同样地,ImageBitmap是一次性的,并且可以分配原生资源,但至少你不会遇到任何锁定问题(AFAIK - 但更安全而不是抱歉)。