ファイルに対する画像を取得する(WPF)

WPFで、単にファイルに対する画像を取得しようとしたのですが、たったこれだけのことにも関わらず、
意外と、どこにもソースが落ちていない。

もちろん、 WindowsAPICodePack を使えば、簡単にできるんですが、大なた過ぎて、ちょっと・・・っていう感じだったり、あるいは、サムネイルを生成したいわけで、アイコン画像が欲しいわけじゃ無いとか、そういうカスタマイズ性がないみたいで、ちょっと困る感じ名部分があります。

というか、C++ならまだ良いんですが、C#だと、IExtractThumbnailとの戦いで消耗している人達ばっかりで、???って感じでした。

ということで、 IShellItemImageFactory を使ってサックリと作ってみました。

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace FileThumbnailExtractor
{
  public static class FileThumbnailExtractor
  {
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SIZE
    {
      public int cx;
      public int cy;
    }

    [Flags]
    enum SIIGBF
    {
      RESIZETOFIT = 0,
      BIGGERSIZEOK = 1,
      MEMORYONLY = 2,
      ICONONLY = 4,
      THUMBNAILONLY = 8,
      INCACHEONLY = 0x10,
      CROPTOSQUARE = 0x20,
      WIDETHUMBNAILS = 0x40,
      ICONBACKGROUND = 0x80,
      SCALEUP = 0x100,
    }

    [ComImport, Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IShellItemImageFactory
    {
      [PreserveSig]
      void GetImage(SIZE size, SIIGBF flags, out IntPtr phbm);
    }

    [DllImport("shell32"), PreserveSig]
    extern static void SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [In] ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object iunk);

    [DllImport("gdi32")]
    extern static int DeleteObject(IntPtr hObject);

    public static BitmapSource GetThumbnail(string fileName, int desiredWidth, int desiredHeight, ThumbnailType type)
    {
      object iunk = null;
      IntPtr hBmp = IntPtr.Zero;
      try
      {
        var IID_IShellItemImageFactory = new Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b");
        SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref IID_IShellItemImageFactory, out iunk);
        var factory = (IShellItemImageFactory)iunk;
        SIIGBF flags = SIIGBF.BIGGERSIZEOK;
        if (type == ThumbnailType.Icon) flags |= SIIGBF.ICONONLY;
        else if (type == ThumbnailType.Thumbnail) flags |= SIIGBF.THUMBNAILONLY;

        factory.GetImage(new SIZE { cx = desiredWidth, cy = desiredHeight }, flags, out hBmp);
        var bmp = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBmp, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        bmp.Freeze();
        return bmp;
      }
      finally
      {
        DeleteObject(hBmp);
        Marshal.ReleaseComObject(iunk);
      }
    }

    public static async Task<BitmapSource> GetThumbnailAsync(string fileName, int desiredWidth, int desiredHeight, ThumbnailType type)
    {
      return await Task.Run(() => GetThumbnail(fileName, desiredWidth, desiredHeight, type));
    }
  }

  [Flags]
  public enum ThumbnailType
  {
    Icon = 1,
    Thumbnail = 2,
    Any = 3,
  }
}