【问题标题】:Extract specific icon layer from file then save it as .ico file with transparency从文件中提取特定的图标层,然后将其保存为具有透明度的 .ico 文件
【发布时间】:2016-05-16 19:04:21
【问题描述】:

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

有很多与图标提取相关的问题,但这是特定于我使用 SHDefExtractIcon 函数应用的以下代码。

我的问题是生成的 .ico 文件的颜色不对,它生成了一种一半可怕的透明度,另一方面,生成的 .png 文件保存得很好。

这是生成的 PNG 文件:

这是生成的 ICO 文件:

这是 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.

【问题讨论】:

  • 假设 PNG 确实保存正确,为什么您怀疑 SHDefExtractIcon API 调用不起作用?另外,您能否详细说明“失败”的含义(屏幕截图会有所帮助)。 (侧节点:如果Image.Save 找不到用于保存的编解码器,it uses png by default。)
  • 对我来说很好。摆脱内存流,只需使用ico.Save(fs) Icon 类型知道如何将自己保存到文件中。
  • 也许只有我一个人,但我认为生成的 .ico 文件看起来很棒。
  • 请注意,使用ToBitmap 并不能保持透明度,很可能是您面临的问题。

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


【解决方案1】:

你需要一个工具

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

所以,我去寻找显示的图像。 Google 为我找到的 Plixia crapware 似乎不一样,所以我下载了 JDownloader,它确实有一个与 cmets 中的图像链接匹配的图标:

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

图标库

我发现了相当多的 C/C++ 代码来管理/处理图标,但也有一个 NET 库可以为您完成此操作。这里有(非常)几个关于这个主题的帖子,但它们似乎都链接到死链接或商业产品。但是,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

结果:

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


资源

MSDN 上的Icons

IconLib 在 CodeProject 上

【讨论】:

【解决方案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+32bit)或64/32bit下单独编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-20
    • 2011-01-05
    • 2023-03-31
    • 1970-01-01
    • 2018-10-19
    • 1970-01-01
    • 1970-01-01
    • 2014-03-28
    相关资源
    最近更新 更多