如何将 IMAGELIST 中的 HICON 正确保存为图像文件

How to properly save an HICON from an IMAGELIST as an image file

我的目标:

HIMAGELIST 中的所有 Window Icon Handle(HICON) 保存为多个图像文件(.png.tiff)。


我的问题:

在我的保存程序之后,有些图像质量很差,但有些则没有。

我只在包含子文件夹/子文件的文件夹图像上注意到这个问题。


我的尝试:

代码背景:

private void Saving()
{
    var hWnd = GetListViewHWnd(); // This is the Desktop SysListView32 HWND

    IntPtr lParam = IntPtr.Zero;
    IntPtr pHil = SendMessage(hWnd, ListViewMessage.LVM_GETIMAGELIST, 0, ref lParam);

    var sHil = new SafeHIMAGELIST(pHil); // This is the IMAGELIST of the ListView

    var imageCount = sHil.Interface.GetImageCount(); // sHil.Interface == IImageList Interface

    for (int i = 0; i < imageCount; i++)
    {
        using (var fs = File.OpenWrite(@"C:\Users\Julien\Desktop\Icons\" + i + ".tiff"))
        {
            using (SafeHICON sHIcon = sHil.Interface.GetIcon(i, IMAGELISTDRAWFLAGS.ILD_NORMAL))
            {
                var bmpS = Imaging.CreateBitmapSourceFromHIcon(
                        sHIcon.DangerousGetHandle(),
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());

                BitmapEncoder enc = new TiffBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bmpS));
                enc.Save(fs);
            }
        }
    }

    sHil.Dispose();
}

还有:

var bmp = Bitmap.FromHicon(sHIcon.DangerousGetHandle());
bmp.Save(fs);

常见问题解答:

为什么我使用列表视图图像列表而不是 SHGetFileInfo

因为 SHGetFileInfo 会给我这样的 HICON :

对于现实中看起来像这样的文件夹:

在你的 SHGetFileInfo 中传递 SHGFI_SYSICONINDEX 怎么样?

同样,非空文件夹的图标不存储在系统图像列表中。

因为我可以用 C++ 编写我的扩展,所以我也愿意接受任何用 C++ 编写的解决方案。

编辑: 我尝试使用 IImageList.Draw() 绘制那些有问题的图像,它似乎有效。很明显问题出在我如何从 HICON.

创建图像

var hdc = GetDC(notepadHWnd);
var dp = new IMAGELISTDRAWPARAMS(
    hdc,
    new RECT(73, 73, 73, 73), 12,
    COLORREF.None,
    IMAGELISTDRAWFLAGS.ILD_NORMAL);

sHil.Interface.Draw(dp);

因为我很固执,所以我坚持使用桌面列表视图的IMAGELIST

我设法从中获取了图标/缩略图。通过在内存设备上下文中绘制它们。

没有更多的错误图像。

private void Saving()
{
    var hWnd = GetListViewHWnd(); // Desktop SysListView32 HWND

    IntPtr lParam = IntPtr.Zero;
    IntPtr pHil = SendMessage(hWnd, ListViewMessage.LVM_GETIMAGELIST, 0, ref lParam);

    var sHil = new SafeHIMAGELIST(pHil); // IMAGELIST of the ListView
    sHil.Interface.GetIconSize(out var cx, out var cy);

    var imageCount = sHil.Interface.GetImageCount(); // IImageList Interface
    var desktopHdc = new SafeHDC(GetDC(GetListViewHWnd()).DangerousGetHandle());
    var inMemoryHdc = CreateCompatibleDC(desktopHdc);

    for (int i = 0; i < imageCount; i++)
    {
        var inMemoryBmp = CreateCompatibleBitmap(desktopHdc, cx, cy);

        SelectObject(inMemoryHdc, inMemoryBmp);

        var ilDp = new IMAGELISTDRAWPARAMS(
            inMemoryHdc, 
            new RECT(0, 0, 0, 0), 
            i, 
            COLORREF.None,
            IMAGELISTDRAWFLAGS.ILD_NORMAL);

        sHil.Interface.Draw(ilDp);

        var bmpS = Imaging.CreateBitmapSourceFromHBitmap(
                inMemoryBmp.DangerousGetHandle(), 
                IntPtr.Zero,
                Int32Rect.Empty, 
                BitmapSizeOptions.FromEmptyOptions());

        using (var fs = File.OpenWrite(@"C:\Users\Julien\Desktop\Icons\" + i + ".png"))
        {
            BitmapEncoder enc = new PngBitmapEncoder();
            enc.Frames.Add(BitmapFrame.Create(bmpS));
            enc.Save(fs);
        }

        inMemoryBmp.Dispose();
    }

    sHil.Dispose();
    desktopHdc.Dispose();
    inMemoryHdc.Dispose();
}

但正如 Jonathan Potter 所说不是一个好主意:

例如 IShellItemImageFactorySHCreateItemFromIDList 的组合似乎更好。