Skip to content

Commit 1cf721a

Browse files
committed
Improved
1 parent 36379fc commit 1cf721a

File tree

4 files changed

+156
-62
lines changed

4 files changed

+156
-62
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,5 @@ SHGetFileInfo
202202
GetFileAttributes
203203
SetFileAttributes
204204
INVALID_FILE_ATTRIBUTES
205+
SHDefExtractIconW
206+
GdipCreateBitmapFromHICON

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Thumbnail.cs renamed to src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
// Copyright (c) Files Community
22
// Licensed under the MIT License.
33

4+
using System.Collections.Concurrent;
45
using System.Runtime.InteropServices;
56
using Windows.Win32;
67
using Windows.Win32.Foundation;
78
using Windows.Win32.Graphics.Gdi;
89
using Windows.Win32.Graphics.GdiPlus;
910
using Windows.Win32.System.Com;
1011
using Windows.Win32.UI.Shell;
12+
using Windows.Win32.UI.WindowsAndMessaging;
1113

1214
namespace Files.App.Storage.Storables
1315
{
1416
public static partial class WindowsStorableHelpers
1517
{
18+
// Fields
19+
1620
private static (Guid Format, Guid Encorder)[]? GdiEncoders;
21+
private static ConcurrentDictionary<(string, int, int), byte[]>? DllIconCache;
22+
23+
// Methods
1724

18-
/// <inheritdoc cref="GetThumbnail"/>
19-
public static async Task<byte[]> GetThumbnailAsync(this IWindowsStorable storable, int size, SIIGBF options)
25+
/// <inheritdoc cref="TryGetThumbnail"/>
26+
public static async Task<byte[]?> GetThumbnailAsync(this IWindowsStorable storable, int size, SIIGBF options)
2027
{
21-
return await STATask.Run(() => storable.GetThumbnail(size, options));
28+
return await STATask.Run(() =>
29+
{
30+
storable.TryGetThumbnail(size, options, out var thumbnailData);
31+
return thumbnailData;
32+
});
2233
}
2334

2435
/// <summary>
@@ -29,32 +40,99 @@ public static async Task<byte[]> GetThumbnailAsync(this IWindowsStorable storabl
2940
/// <param name="options">A combination of <see cref="SIIGBF"/> flags that specify how the thumbnail should be retrieved.</param>
3041
/// <returns>A byte array containing the thumbnail image in its native format (e.g., PNG, JPEG).</returns>
3142
/// <remarks>If the thumbnail is JPEG, this tries to decoded as a PNG instead because JPEG loses data.</remarks>
32-
public unsafe static byte[] GetThumbnail(this IWindowsStorable storable, int size, SIIGBF options)
43+
public unsafe static bool TryGetThumbnail(this IWindowsStorable storable, int size, SIIGBF options, out byte[]? thumbnailData)
3344
{
45+
thumbnailData = null;
46+
3447
using ComPtr<IShellItemImageFactory> pShellItemImageFactory = storable.ThisPtr.As<IShellItemImageFactory>();
3548
if (pShellItemImageFactory.IsNull)
36-
return [];
49+
return false;
3750

3851
// Get HBITMAP
3952
HBITMAP hBitmap = default;
4053
HRESULT hr = pShellItemImageFactory.Get()->GetImage(new(size, size), options, &hBitmap);
4154
if (hr.ThrowIfFailedOnDebug().Failed)
4255
{
4356
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
44-
return [];
57+
return false;
4558
}
4659

4760
// Convert to GpBitmap of GDI+
4861
GpBitmap* gpBitmap = default;
49-
PInvoke.GdipCreateBitmapFromHBITMAP(hBitmap, HPALETTE.Null, &gpBitmap);
62+
if (PInvoke.GdipCreateBitmapFromHBITMAP(hBitmap, HPALETTE.Null, &gpBitmap) is not Status.Ok)
63+
{
64+
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
65+
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
66+
return false;
67+
}
68+
69+
if (TryConvertGpBitmapToByteArray(gpBitmap, out thumbnailData))
70+
{
71+
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
72+
return false;
73+
}
74+
75+
return true;
76+
}
77+
78+
public unsafe static bool TryExtractImageFromDll(this IWindowsStorable storable, int size, int index, out byte[]? imageData)
79+
{
80+
DllIconCache ??= [];
81+
imageData = null;
82+
83+
if (storable.ToString() is not { } path)
84+
return false;
85+
86+
if (DllIconCache.TryGetValue((path, index, size), out var cachedImageData))
87+
{
88+
imageData = cachedImageData;
89+
return true;
90+
}
91+
else
92+
{
93+
HICON hIcon = default;
94+
HRESULT hr = default;
95+
96+
fixed (char* pszPath = path)
97+
hr = PInvoke.SHDefExtractIcon(pszPath, -1 * index, 0, &hIcon, null, (uint)size);
98+
99+
if (hr.ThrowIfFailedOnDebug().Failed)
100+
{
101+
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
102+
return false;
103+
}
104+
105+
// Convert to GpBitmap of GDI+
106+
GpBitmap* gpBitmap = default;
107+
if (PInvoke.GdipCreateBitmapFromHICON(hIcon, &gpBitmap) is not Status.Ok)
108+
{
109+
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
110+
return false;
111+
}
112+
113+
if (!TryConvertGpBitmapToByteArray(gpBitmap, out imageData))
114+
{
115+
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
116+
return false;
117+
}
118+
119+
DllIconCache[(path, index, size)] = imageData;
120+
PInvoke.DestroyIcon(hIcon);
121+
122+
return true;
123+
}
124+
}
125+
126+
public unsafe static bool TryConvertGpBitmapToByteArray(GpBitmap* gpBitmap, out byte[]? imageData)
127+
{
128+
imageData = null;
50129

51130
// Get an encoder for PNG
52131
Guid format = Guid.Empty;
53132
if (PInvoke.GdipGetImageRawFormat((GpImage*)gpBitmap, &format) is not Status.Ok)
54133
{
55134
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
56-
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
57-
return [];
135+
return false;
58136
}
59137

60138
Guid encoder = GetEncoderClsid(format);
@@ -65,28 +143,25 @@ public unsafe static byte[] GetThumbnail(this IWindowsStorable storable, int siz
65143
}
66144

67145
using ComPtr<IStream> pStream = default;
68-
hr = PInvoke.CreateStreamOnHGlobal(HGLOBAL.Null, true, pStream.GetAddressOf());
146+
HRESULT hr = PInvoke.CreateStreamOnHGlobal(HGLOBAL.Null, true, pStream.GetAddressOf());
69147
if (hr.ThrowIfFailedOnDebug().Failed)
70148
{
71149
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
72-
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
73-
return [];
150+
return false;
74151
}
75152

76153
if (PInvoke.GdipSaveImageToStream((GpImage*)gpBitmap, pStream.Get(), &encoder, (EncoderParameters*)null) is not Status.Ok)
77154
{
78155
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
79-
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
80-
return [];
156+
return false;
81157
}
82158

83159
STATSTG stat = default;
84160
hr = pStream.Get()->Stat(&stat, (uint)STATFLAG.STATFLAG_NONAME);
85161
if (hr.ThrowIfFailedOnDebug().Failed)
86162
{
87163
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
88-
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
89-
return [];
164+
return false;
90165
}
91166

92167
ulong statSize = stat.cbSize & 0xFFFFFFFF;
@@ -97,15 +172,14 @@ public unsafe static byte[] GetThumbnail(this IWindowsStorable storable, int siz
97172
if (hr.ThrowIfFailedOnDebug().Failed)
98173
{
99174
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
100-
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
101175
if (RawThumbnailData is not null) NativeMemory.Free(RawThumbnailData);
102-
return [];
176+
return false;
103177
}
104178

105-
byte[] thumbnailData = new ReadOnlySpan<byte>(RawThumbnailData, (int)statSize / sizeof(byte)).ToArray();
179+
imageData = new ReadOnlySpan<byte>(RawThumbnailData, (int)statSize / sizeof(byte)).ToArray();
106180
NativeMemory.Free(RawThumbnailData);
107181

108-
return thumbnailData;
182+
return true;
109183

110184
Guid GetEncoderClsid(Guid format)
111185
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using Windows.Win32;
5+
using Windows.Win32.Foundation;
6+
using Windows.Win32.Storage.FileSystem;
7+
using Windows.Win32.System.SystemServices;
8+
using Windows.Win32.UI.Shell;
9+
10+
namespace Files.App.Storage.Storables
11+
{
12+
public static partial class WindowsStorableHelpers
13+
{
14+
public unsafe static HRESULT GetPropertyValue<TValue>(this IWindowsStorable storable, string propKey, out TValue value)
15+
{
16+
using ComPtr<IShellItem2> pShellItem2 = default;
17+
var shellItem2Iid = typeof(IShellItem2).GUID;
18+
HRESULT hr = storable.ThisPtr.Get()->QueryInterface(&shellItem2Iid, (void**)pShellItem2.GetAddressOf());
19+
hr = PInvoke.PSGetPropertyKeyFromName(propKey, out var originalPathPropertyKey);
20+
hr = pShellItem2.Get()->GetString(originalPathPropertyKey, out var szOriginalPath);
21+
22+
if (typeof(TValue) == typeof(string))
23+
{
24+
value = (TValue)(object)szOriginalPath.ToString();
25+
return hr;
26+
}
27+
else
28+
{
29+
value = default!;
30+
return HRESULT.E_FAIL;
31+
}
32+
}
33+
34+
public unsafe static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes)
35+
{
36+
return storable.ThisPtr.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded &&
37+
returnedAttributes == attributes;
38+
}
39+
40+
public unsafe static bool HasShellAttributes(this ComPtr<IShellItem> pShellItem, SFGAO_FLAGS attributes)
41+
{
42+
return pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded &&
43+
returnedAttributes == attributes;
44+
}
45+
46+
public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH)
47+
{
48+
using ComHeapPtr<PWSTR> pszName = default;
49+
HRESULT hr = storable.ThisPtr.Get()->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf());
50+
51+
return hr.ThrowIfFailedOnDebug().Succeeded
52+
? (*pszName.Get()).ToString()
53+
: string.Empty;
54+
}
55+
}
56+
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.General.cs renamed to src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,20 @@
11
// Copyright (c) Files Community
22
// Licensed under the MIT License.
33

4+
using System.Collections.Concurrent;
5+
using Windows.Networking.BackgroundTransfer;
6+
using Windows.System;
47
using Windows.Win32;
58
using Windows.Win32.Foundation;
69
using Windows.Win32.Storage.FileSystem;
710
using Windows.Win32.System.SystemServices;
811
using Windows.Win32.UI.Shell;
12+
using Windows.Win32.UI.WindowsAndMessaging;
913

1014
namespace Files.App.Storage.Storables
1115
{
1216
public static partial class WindowsStorableHelpers
1317
{
14-
public unsafe static HRESULT GetPropertyValue<TValue>(this IWindowsStorable storable, string propKey, out TValue value)
15-
{
16-
using ComPtr<IShellItem2> pShellItem2 = default;
17-
var shellItem2Iid = typeof(IShellItem2).GUID;
18-
HRESULT hr = storable.ThisPtr.Get()->QueryInterface(&shellItem2Iid, (void**)pShellItem2.GetAddressOf());
19-
hr = PInvoke.PSGetPropertyKeyFromName(propKey, out var originalPathPropertyKey);
20-
hr = pShellItem2.Get()->GetString(originalPathPropertyKey, out var szOriginalPath);
21-
22-
if (typeof(TValue) == typeof(string))
23-
{
24-
value = (TValue)(object)szOriginalPath.ToString();
25-
return hr;
26-
}
27-
else
28-
{
29-
value = default!;
30-
return HRESULT.E_FAIL;
31-
}
32-
}
33-
34-
public unsafe static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes)
35-
{
36-
return storable.ThisPtr.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded &&
37-
returnedAttributes == attributes;
38-
}
39-
40-
public unsafe static bool HasShellAttributes(this ComPtr<IShellItem> pShellItem, SFGAO_FLAGS attributes)
41-
{
42-
return pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded &&
43-
returnedAttributes == attributes;
44-
}
45-
46-
public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH)
47-
{
48-
using ComHeapPtr<PWSTR> pszName = default;
49-
HRESULT hr = storable.ThisPtr.Get()->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf());
50-
51-
return hr.ThrowIfFailedOnDebug().Succeeded
52-
? (*pszName.Get()).ToString()
53-
: string.Empty;
54-
}
55-
5618
public static bool TryGetFileAttributes(this IWindowsStorable storable, out FILE_FLAGS_AND_ATTRIBUTES attributes)
5719
{
5820
attributes = (FILE_FLAGS_AND_ATTRIBUTES)PInvoke.GetFileAttributes(storable.GetDisplayName());

0 commit comments

Comments
 (0)