从文件中提取特定图标图层,然后将其另存为具有透明度的.ico文件

时间:2016-05-16 19:04:21

标签: c# .net vb.net winapi icons

正如问题标题所示,我正在尝试从文件中提取特定的图标图层,然后将其保存为具有透明度的ico文件(如源图标所示)。

有许多与图标提取相关的问题,但这是针对以下使用 SHDefExtractIcon 功能的代码所特有的。

我遇到的问题是生成的.ico文件的颜色是错误的,它产生了一种半透明的可怕透明度,另一方面,生成的.png文件被完美保存。

这是生成的PNG文件:

enter image description here

这是生成的ICO文件:

enter image description here

这是Windows API的限制,还是我做错了什么?。

C#:

[DllImport("Shell32.dll", SetLastError = false)]
public static extern int SHDefExtractIcon(string iconFile, int iconIndex, uint flags, ref IntPtr hiconLarge, ref IntPtr hiconSmall, uint iconSize);

IntPtr hiconLarge = default(IntPtr);
SHDefExtractIcon("C:\\file.exe", 0, 0, hiconLarge, null, 256);
// ToDO: Handle HRESULT.

Icon ico = Icon.FromHandle(hiconLarge);
Bitmap bmp = ico.ToBitmap();

// Save as .png with transparency. success.
bmp.Save("C:\\ico.png", ImageFormat.Png);

// 1st intent: Save as .ico with transparency. failure. 
//' Transparency is ok but it generates a false icon, it's .png with modified extension to .ico.
bmp.Save("C:\\ico1.ico", ImageFormat.Icon);

// 2nd intent: Save as .ico with transparency. failure. Wrong transparency.
using (MemoryStream ms = new MemoryStream()) {
    ico.Save(ms);
    using (FileStream fs = new FileStream("C:\\ico2.ico", FileMode.CreateNew)) {
        ms.WriteTo(fs);
    }
    // ToDO: Destroy hiconLarge here with DestroyIcon function.
}

VB.NET:

Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

<DllImport("Shell32.dll", SetLastError:=False)>
Public Shared Function SHDefExtractIcon(ByVal iconFile As String,
                                        ByVal iconIndex As Integer,
                                        ByVal flags As UInteger,
                                        ByRef hiconLarge As IntPtr,
                                        ByRef hiconSmall As IntPtr,
                                        ByVal iconSize As UInteger
) As Integer
End Function


    Dim hiconLarge As IntPtr
    SHDefExtractIcon("C:\file.exe", 0, 0, hiconLarge, Nothing, 256)
    ' ToDO: Handle HRESULT.

    Dim ico As Icon = Icon.FromHandle(hiconLarge)
    Dim bmp As Bitmap = ico.ToBitmap()

    ' Save as .png with transparency. success.
    bmp.Save("C:\ico.png", ImageFormat.Png)

    ' 1st intent: Save as .ico with transparency. failure. 
    ' Transparency is ok but it generates a false icon, it's .png with modified extension to .ico.
    bmp.Save("C:\ico1.ico", ImageFormat.Icon)

    ' 2nd intent: Save as .ico with transparency. failure. Wrong transparency.
    Using ms As New MemoryStream
        ico.Save(ms)
        Using fs As New FileStream("C:\ico2.ico", FileMode.CreateNew)
            ms.WriteTo(fs)
        End Using
    End Using

    ' ToDO: Destroy hiconLarge here with DestroyIcon function.

2 个答案:

答案 0 :(得分:5)

您需要一个工具

没有人可以重现这个问题的原因是它需要一个精致的艺术图标。一个简单的图标将使从ico到磁盘的行程变得很好并保持透明度。我们也不知道你做了什么来获得质量差的图像 - 这是一个JPG,所以有一个图标到JPG转换。

所以,我去寻找所显示的图像。谷歌找到的Plixia crapware似乎不一样,所以我下载了JDownloader,它在评论中有一个与图片链接匹配的图标:

enter image description here

不仅透明度丢失,它看起来一直很糟糕,它在资源管理器中的图像视图同样糟糕。我们都知道ICO文件是一个可容纳大量图像的容器,但事实证明,Icons are complex critters并没有用于详细/复杂图像的高质量编码器。

IconLib

我找到了相当数量的C / C ++代码来管理/争论图标,但是还有一个NET lib可以为你做这个。这个主题的帖子很少(很少),但它们似乎都与死链接或商业产品有关。但是,IconLib from CodeProject似乎可以做你想要的。简要地看一下创建/绘制图标的源代码,它看起来非常像C / C ++代码。

它可用于进行提取并将图标保存到集合中。我忽略了那一部分,因为这个问题似乎只对SHDefExtractIcon感兴趣。它可能有点过时,你可能想为.NET 4.xxx重新编译它;但似乎确实写出了高质量的图标。代码以SHDefExtractIcon中的图标开头,将其保存到文件并显示文件版本:

Dim file As String = "C:\Temp\electro crapware\JDownloader2.exe"

Dim hiconLarge As IntPtr
NativeMethods.SHDefExtractIcon(file, 0, 0, hiconLarge, Nothing, 256)

Dim ico As Icon = Icon.FromHandle(hiconLarge)
Dim bmp As Bitmap = ico.ToBitmap()
pbIcoA.Image = bmp

' png version
bmp.Save("C:\Temp\electro.png", ImageFormat.Png)

'' simple NET ico save: Doesnt Work! (== low quality image)
'Using fs As New FileStream("C:\Temp\electro.ico", FileMode.OpenOrCreate)
'    ico.Save(fs)
'End Using

'' Imports System.Drawing.IconLib
Dim myIcons As New MultiIcon()
Dim sIco As SingleIcon = myIcons.Add("electro")

' create Icon from the PInvoked ico.ToBitmap()
' note that enum only goes "up" to Vista
sIco.CreateFrom(bmp, IconOutputFormat.Vista)
sIco.Save("C:\Temp\electro2.ico")

' now display image from disk ico
Dim bmpX As Bitmap
Using icob As New Icon("C:\Temp\electro2.ico")
    bmpX = icob.ToBitmap
End Using
pbIcoB.Image = bmpX

' ToDo: Dispose

结果:

enter image description here enter image description here

您可能会使用SingleIcon.CreateFromSave方法来创建自己的版本,或者使用它来代替PInvoke。我本来喜欢在蛇/蠕虫图像上进行测试,因为它背景看起来不仅仅是失去了透明度。

<强>资源

MSDN上的

Icons

CodeProject上的

IconLib

答案 1 :(得分:2)

我试图重现你的问题,但没有原来的exe或图标文件,很难这样做。我过去确实遇到过这个问题。在某些情况下,在Visual Basic或C#中保存图标(特别是在旧版本(如2008或2010)上)以256色图标结束。我最终通过寻找一个外部图像管理库(如FreeImage)来解决这个问题,我将其插入到我的项目中以替换MS。

FreeImage是100%开源的,可在此处获取:http://freeimage.sourceforge.net/(请参阅下面有关如何在项目中引用它的说明)。

以下是如何在VB.net中使用它的示例:

<DllImport("Shell32.dll", SetLastError:=False)>
Public Shared Function SHDefExtractIcon(ByVal iconFile As String,
                                        ByVal iconIndex As Integer,
                                        ByVal flags As UInteger,
                                        ByRef hiconLarge As IntPtr,
                                        ByRef hiconSmall As IntPtr,
                                        ByVal iconSize As UInteger
) As Integer
End Function


Dim hiconLarge As IntPtr
SHDefExtractIcon("C:\file.exe", 0, 0, hiconLarge, Nothing, 256)

Dim ico As Icon = Icon.FromHandle(hiconLarge)
Dim bmp As Bitmap = ico.ToBitmap()
Dim fiBmp As FreeImageAPI.FreeImageBitmap = New FreeImageAPI.FreeImageBitmap(bmp)

' At this point you can either save a simple one size icon file or you can 
' take advantage of the FreeImage engine to save a multi-sized icon:

fiBmp.Save("C:\ico1.ico", FreeImageAPI.FREE_IMAGE_FORMAT.FIF_ICO)

' To add more layers to your Icon:
fiBmp.Rescale(128, 128, FreeImageAPI.FREE_IMAGE_FILTER.FILTER_LANCZOS3)
fiBmp.SaveAdd("C:\ico1.ico")

fiBmp.Rescale(64, 64, FreeImageAPI.FREE_IMAGE_FILTER.FILTER_LANCZOS3)
fiBmp.SaveAdd("C:\ico1.ico")

fiBmp.Rescale(48, 48, FreeImageAPI.FREE_IMAGE_FILTER.FILTER_LANCZOS3)
fiBmp.SaveAdd("C:\ico1.ico")    

'etc etc etc

这就是将FreeImage插入项目的方法。

  • 从项目网站下载源代码并进行编译。如果您的项目是64位,请务必编译为64位,反之亦然.32。
  • 找到FreeImage.dll并将其移动到您的C#或vb.net项目文件夹。
  • Visual Studio中的.Net项目无法直接引用此dll。您还必须构建FreeImageNET.dll,它包含在与FreeImage.dll相同的文件夹中,并在Visual Studio中引用。 FreeImageNET.dll的源代码可以在Wrapper / FreeImage.NET / cs下查找。 FreeImageNET.dll可以在anycpu架构(64 + 32位)或64 / 32bit下单独编译。