2018-03-31 07:19:55 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
using System.IO;
|
2024-12-10 12:44:28 +00:00
|
|
|
|
using System.Windows;
|
2018-03-31 07:19:55 +00:00
|
|
|
|
using System.Windows.Interop;
|
|
|
|
|
|
using System.Windows.Media.Imaging;
|
2024-12-10 12:44:28 +00:00
|
|
|
|
using Windows.Win32;
|
|
|
|
|
|
using Windows.Win32.Foundation;
|
|
|
|
|
|
using Windows.Win32.UI.Shell;
|
|
|
|
|
|
using Windows.Win32.Graphics.Gdi;
|
2018-03-31 07:19:55 +00:00
|
|
|
|
|
2020-04-21 09:12:17 +00:00
|
|
|
|
namespace Flow.Launcher.Infrastructure.Image
|
2018-03-31 07:19:55 +00:00
|
|
|
|
{
|
2024-12-10 12:44:28 +00:00
|
|
|
|
/// <summary>
|
2025-04-15 04:52:09 +00:00
|
|
|
|
/// Subclass of <see cref="SIIGBF"/>
|
2024-12-10 12:44:28 +00:00
|
|
|
|
/// </summary>
|
2018-03-31 07:19:55 +00:00
|
|
|
|
[Flags]
|
|
|
|
|
|
public enum ThumbnailOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
None = 0x00,
|
|
|
|
|
|
BiggerSizeOk = 0x01,
|
|
|
|
|
|
InMemoryOnly = 0x02,
|
|
|
|
|
|
IconOnly = 0x04,
|
|
|
|
|
|
ThumbnailOnly = 0x08,
|
|
|
|
|
|
InCacheOnly = 0x10,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class WindowsThumbnailProvider
|
|
|
|
|
|
{
|
|
|
|
|
|
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
|
|
|
|
|
|
|
2024-12-10 12:44:28 +00:00
|
|
|
|
private static readonly Guid GUID_IShellItem = typeof(IShellItem).GUID;
|
2018-03-31 07:19:55 +00:00
|
|
|
|
|
2025-04-15 04:59:58 +00:00
|
|
|
|
private static readonly HRESULT S_EXTRACTIONFAILED = (HRESULT)0x8004B200;
|
2025-02-17 11:07:01 +00:00
|
|
|
|
|
2025-04-15 04:52:09 +00:00
|
|
|
|
private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205;
|
|
|
|
|
|
|
2018-03-31 07:19:55 +00:00
|
|
|
|
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
|
|
|
|
|
|
{
|
2024-12-10 12:44:28 +00:00
|
|
|
|
HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
|
2018-03-31 07:19:55 +00:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
// delete HBitmap to avoid memory leaks
|
2024-12-10 12:44:28 +00:00
|
|
|
|
PInvoke.DeleteObject(hBitmap);
|
2018-03-31 07:19:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-10 12:44:28 +00:00
|
|
|
|
|
|
|
|
|
|
private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
|
2018-03-31 07:19:55 +00:00
|
|
|
|
{
|
2024-12-10 12:44:28 +00:00
|
|
|
|
var retCode = PInvoke.SHCreateItemFromParsingName(
|
|
|
|
|
|
fileName,
|
|
|
|
|
|
null,
|
|
|
|
|
|
GUID_IShellItem,
|
|
|
|
|
|
out var nativeShellItem);
|
2018-03-31 07:19:55 +00:00
|
|
|
|
|
2024-12-10 12:44:28 +00:00
|
|
|
|
if (retCode != HRESULT.S_OK)
|
2018-03-31 07:19:55 +00:00
|
|
|
|
throw Marshal.GetExceptionForHR(retCode);
|
|
|
|
|
|
|
2024-12-10 12:44:28 +00:00
|
|
|
|
if (nativeShellItem is not IShellItemImageFactory imageFactory)
|
2018-03-31 07:19:55 +00:00
|
|
|
|
{
|
2024-12-10 12:44:28 +00:00
|
|
|
|
Marshal.ReleaseComObject(nativeShellItem);
|
2024-12-11 03:08:57 +00:00
|
|
|
|
nativeShellItem = null;
|
2024-12-10 12:44:28 +00:00
|
|
|
|
throw new InvalidOperationException("Failed to get IShellItemImageFactory");
|
|
|
|
|
|
}
|
2018-03-31 07:19:55 +00:00
|
|
|
|
|
2024-12-10 12:44:28 +00:00
|
|
|
|
SIZE size = new SIZE
|
|
|
|
|
|
{
|
|
|
|
|
|
cx = width,
|
|
|
|
|
|
cy = height
|
|
|
|
|
|
};
|
2018-03-31 07:19:55 +00:00
|
|
|
|
|
2024-12-10 12:44:28 +00:00
|
|
|
|
HBITMAP hBitmap = default;
|
|
|
|
|
|
try
|
2018-03-31 07:19:55 +00:00
|
|
|
|
{
|
2024-12-10 12:44:28 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
imageFactory.GetImage(size, (SIIGBF)options, &hBitmap);
|
|
|
|
|
|
}
|
2025-04-15 04:52:09 +00:00
|
|
|
|
catch (COMException ex) when (options == ThumbnailOptions.ThumbnailOnly &&
|
2025-04-15 04:59:58 +00:00
|
|
|
|
(ex.HResult == S_PATHNOTFOUND || ex.HResult == S_EXTRACTIONFAILED))
|
2025-02-16 14:47:10 +00:00
|
|
|
|
{
|
2025-04-15 04:52:09 +00:00
|
|
|
|
// Fallback to IconOnly if extraction fails or files cannot be found
|
2025-02-16 14:47:10 +00:00
|
|
|
|
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
|
|
|
|
|
}
|
2025-02-17 11:07:01 +00:00
|
|
|
|
catch (FileNotFoundException) when (options == ThumbnailOptions.ThumbnailOnly)
|
2024-12-10 12:44:28 +00:00
|
|
|
|
{
|
2025-02-16 14:50:43 +00:00
|
|
|
|
// Fallback to IconOnly if files cannot be found
|
2024-12-10 12:44:28 +00:00
|
|
|
|
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
|
|
|
|
|
}
|
2025-04-15 04:52:09 +00:00
|
|
|
|
catch (System.Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Handle other exceptions
|
|
|
|
|
|
throw new InvalidOperationException("Failed to get thumbnail", ex);
|
|
|
|
|
|
}
|
2024-12-10 12:44:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-12-11 03:08:57 +00:00
|
|
|
|
if (nativeShellItem != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Marshal.ReleaseComObject(nativeShellItem);
|
|
|
|
|
|
}
|
2018-03-31 07:19:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-10 12:44:28 +00:00
|
|
|
|
return hBitmap;
|
2018-03-31 07:19:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-10 12:44:28 +00:00
|
|
|
|
}
|