学习

在 C# 中将 Image 文件转换成 Icon 文件

最近写程序时,要给程序做一个 ico 图标,以前都是画好在网上找在线转换程序。但每次都上网找,其一是麻烦,其二,当没网的时候,就没有办法了。于是,我准备自己写一个程序来帮我做这些事情。 这虽然看起来是很简单的一个问题,但是却遇到了种种奇怪的问题,接下来我将详细说一下。

使用.NET 自带的函数

.NET 的 Bitmap 类有一个 Bitmap.Save()函数,此函数可以把 bitmap 转换成指定类型的图片,其中包括 Icon,了解之后接下来就好办了。 首先,把 Image 转成 Bitmap:

var image = Image.FromFile(filePath);
var bitmap = new Bitmap(image);

接下来,使用 Bitmap 内置函数将 Bitmap 转换成 Icon:

bitmap.Save(fileName,ImageFormat.Icon);

这样,就完成了将 Image 到 Icon 的转换。 但是,这样做有一些问题,在此说明一下: 这样做的话,在资源管理器和图片浏览器中,可以正常显示图标。但是,当用 Winhex 打开此图标后,我发现文件头根本不是 Icon 的文件头,而是 Png 文件,Winhex 也很明确的说明了,这不是一个图标文件。
1
2
然后,就有了下边的想法:

手动将 Image 转换成 Icon 格式

这步几乎是没有出现什么问题的,谷歌了一下,出现了好多前人写的文章,大概思路都是,手动写入 Icon 的文件头,然后再将 Image 的数据写入,这样,就可以转换成 Icon。 在参考了几篇文章后,写出如下代码:

public static Icon ConvertToIcon(Image image)
{
    Icon icon;
    using (var msImg = new MemoryStream())
    {
        using (var msIco = new MemoryStream())
        {
            image.Save(msImg, ImageFormat.Png);
            using (var bin = new BinaryWriter(msIco))
            {
                Byte[] header =
                {
                    (Byte)0,(Byte)0,
                    (Byte)1,(Byte)0,
                    (Byte)1,(Byte)0,
                };

                bin.Write(header);
                bin.Write((Byte)image.Width);
                bin.Write((Byte)image.Height);
                bin.Write((Byte)0);
                bin.Write((Byte)0);
                bin.Write((Int16)1);
                bin.Write((Int16)32);
                bin.Write((Int32)msImg.Length);
                bin.Write((Int32)22);
                bin.Write(msImg.ToArray());
                bin.Flush();
                bin.Seek(0, SeekOrigin.Begin);

                icon =  new Icon(msIco);
            }
        }
    }
    return icon;
}

这样,就完成了从 Image 到 Icon 的手动转变,但是接下来的步骤再一次出现了问题。

将 Icon 写入到本地文件

这一步我是没有想到会出问题的,建立一个 FileStream 流,利用 Icon.Save()函数将 Icon 存入流,然后将流保存到本地就可以了。

var icon = ConvertToIcon(image);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
    icon.Save(fileStream);
    fileStream.Flush();
}

但是,偏偏这一步出现了一个意料之外的问题:在这样写入之后,在资源管理器和图片浏览器里,无法正常显示图标,会识别为无法识别的格式。
3
但是,用 Winhex 打开后,则显示其为 Icon 文件,且预览图可以正常显示。
4
5
此问题困扰了我好久,在多次测试之后,我发现,只有 Png 格式的图片在这样转换成 Icon 后,才可以正常在资源管理器中显示,在被其他程序所识别并使用,而 Bmp,Jpg 等格式转换后,则会出现无法识别的情况。 所以,多做一次转换,先将 Image 转换成 Bitmap,然后再转换成 Png 格式,写出如下代码:

using (Bitmap bmp = new Bitmap(image, new Size(image.Width, image.Height)))
{
    bmp.Save(msImg, ImageFormat.Png);
}

这样,转换后的 Icon 文件才是正确的 Icon 文件,然后,建个 FileStream 流写入即可:
6
7

总结

将以上代码综合起来,完整转换代码如下:

private void btnOpen_Click(Object sender, RoutedEventArgs e){
    OpenFileDialog dlg = new OpenFileDialog()
    {
        Filter = "PNG,JPG,BMP|*.png;*.jpg;*.bmp"
    };
    var result = dlg.ShowDialog();
    if (result == false)
    {
        return;
    }
    var image = Image.FromFile(dlg.FileName);
    var icon = ConvertToIcon(image);
    using (var fileStream = new FileStream(dlg.FileName + ".ico", FileMode.Create))
    {
        icon.Save(fileStream);
        fileStream.Flush();
    }
}

public static Icon ConvertToIcon(Image image)
{
    Icon icon;
    using (var msImg = new MemoryStream())
    {
        using (var msIco = new MemoryStream())
        {
            using (Bitmap bmp = new Bitmap(image, new Size(image.Width, image.Height)))
            {
                bmp.Save(msImg, ImageFormat.Png);
            }
            using (var bin = new BinaryWriter(msIco))
            {
                Byte[] header =
                {
                    (Byte)0,(Byte)0,
                    (Byte)1,(Byte)0,
                    (Byte)1,(Byte)0,
                };

                bin.Write(header);
                bin.Write((Byte)image.Width);
                bin.Write((Byte)image.Height);
                bin.Write((Byte)0);
                bin.Write((Byte)0);
                bin.Write((Int16)1);
                bin.Write((Int16)32);
                bin.Write((Int32)msImg.Length);
                bin.Write((Int32)22);
                bin.Write(msImg.ToArray());
                bin.Flush();
                bin.Seek(0, SeekOrigin.Begin);

                icon =  new Icon(msIco);
            }
        }
    }
    return icon;
}

当然,在进行转换时,还有一系列刷工作,比如判断所选文件是否为 Image,异常检测等,此处并没有写明。

后记

虽然以上代码已经可以几乎完美转换 Image 为 Icon 文件了,但是后来在修改中发现了一个问题:在加载图片文件后,除非程序关闭,否则无法 正常释放图片文件,经查错后,发现是 Image.FromFile()这里出现的问题,用这个函数读取图片时,会无法释放,解决方法为,将文件以流形式读取后,在转换为 Image:

using (var fileStream = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read))
{
    this._image = Image.FromStream(fileStream);
}

这样做的话,就能解决一直占用的问题了。
完整项目详见Github: Image2Ico 如有错误或不合理之处,欢迎批评指出。

© 本作品采用CC BY 4.0协议进行许可。非原创(转载)文章版权归原作者所有。
© 此文章可随意转载引用,但请注明来源: 在 C# 中将 Image 文件转换成 Icon 文件


不说点什么吗

77 + = 85