Adds support for JUMBO thumbnails in the helper (#38539)

Adds a parameter to `Toolkit.ThumbnailHelper.GetThumbnail` to retrieve the largest possible icon from the file. For most use cases, the normal icon size will be good for list items and page icons. 

But for details, you'll want to use the JUMBO icons, and to retrieve them, we need to get the icon from a different API. 

As a drive-by, I also have us fetching the highest-res app icon for UWP's rather than the lowest-res icon.

Solves #38238 

Screenshots:
| before | after | 
| ------ | ----- |
| ![image](https://github.com/user-attachments/assets/8aebf163-2f71-45c5-9bee-052ef5528c58) | ![image](https://github.com/user-attachments/assets/7d7b417a-d8d0-4234-ad2b-446a4ca804ba) |
| ![image](https://github.com/user-attachments/assets/3aa21305-2d5f-40a5-a091-fbe5ca5f332c) | ![image](https://github.com/user-attachments/assets/beb5e62f-c649-4cbc-8f6e-8d2c1655cac0) |
This commit is contained in:
Mike Griese
2025-04-16 12:04:46 -05:00
committed by GitHub
parent c7789abf04
commit f65a3fc06f
6 changed files with 129 additions and 48 deletions

View File

@@ -27,4 +27,10 @@ internal sealed class NativeMethods
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool DestroyIcon(IntPtr hIcon);
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
internal static extern int SHGetImageList(int iImageList, ref Guid riid, out IntPtr ppv);
[DllImport("comctl32.dll", SetLastError = true)]
internal static extern int ImageList_GetIcon(IntPtr himl, int i, int flags);
}

View File

@@ -24,19 +24,12 @@ public class ThumbnailHelper
".ico",
];
public static Task<IRandomAccessStream?> GetThumbnail(string path)
public static Task<IRandomAccessStream?> GetThumbnail(string path, bool jumbo = false)
{
var extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
try
{
if (ImageExtensions.Contains(extension))
{
return GetImageThumbnailAsync(path);
}
else
{
return GetFileIconStream(path);
}
return ImageExtensions.Contains(extension) ? GetImageThumbnailAsync(path) : GetFileIconStream(path, jumbo);
}
catch (Exception)
{
@@ -45,9 +38,19 @@ public class ThumbnailHelper
return Task.FromResult<IRandomAccessStream?>(null);
}
private const uint SHGFIICON = 0x000000100;
private const uint SHGFILARGEICON = 0x000000000;
// these are windows constants and mangling them is goofy
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private const uint SHGFI_ICON = 0x000000100;
private const uint SHGFI_SHELLICONSIZE = 0x000000004;
private const int SHGFI_SYSICONINDEX = 0x000004000;
private const int SHIL_JUMBO = 4;
private const int ILD_TRANSPARENT = 1;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
#pragma warning restore SA1310 // Field names should not contain underscore
// This will call DestroyIcon on the hIcon passed in.
// Duplicate it if you need it again after this.
private static MemoryStream GetMemoryStreamFromIcon(IntPtr hIcon)
{
var memoryStream = new MemoryStream();
@@ -65,19 +68,40 @@ public class ThumbnailHelper
return memoryStream;
}
private static async Task<IRandomAccessStream?> GetFileIconStream(string filePath)
private static async Task<IRandomAccessStream?> GetFileIconStream(string filePath, bool jumbo)
{
var shinfo = default(NativeMethods.SHFILEINFO);
var hr = NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFIICON | SHGFILARGEICON);
nint hIcon = 0;
if (hr == 0 || shinfo.hIcon == 0)
// If requested, look up the Jumbo icon
if (jumbo)
{
hIcon = GetLargestIcon(filePath);
}
// If we didn't want the JUMBO icon, or didn't find it, fall back to
// the normal icon lookup
if (hIcon == 0)
{
var shinfo = default(NativeMethods.SHFILEINFO);
var hr = NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SHELLICONSIZE);
if (hr == 0 || shinfo.hIcon == 0)
{
return null;
}
hIcon = shinfo.hIcon;
}
if (hIcon == 0)
{
return null;
}
var stream = new InMemoryRandomAccessStream();
using var memoryStream = GetMemoryStreamFromIcon(shinfo.hIcon);
using var memoryStream = GetMemoryStreamFromIcon(hIcon); // this will DestroyIcon hIcon
using var outputStream = stream.GetOutputStreamAt(0);
using (var dataWriter = new DataWriter(outputStream))
{
@@ -95,4 +119,21 @@ public class ThumbnailHelper
var thumbnail = await file.GetThumbnailAsync(ThumbnailMode.PicturesView);
return thumbnail;
}
private static nint GetLargestIcon(string path)
{
var shinfo = default(NativeMethods.SHFILEINFO);
NativeMethods.SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SYSICONINDEX);
var hIcon = IntPtr.Zero;
var iID_IImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IntPtr imageListPtr;
if (NativeMethods.SHGetImageList(SHIL_JUMBO, ref iID_IImageList, out imageListPtr) == 0 && imageListPtr != IntPtr.Zero)
{
hIcon = NativeMethods.ImageList_GetIcon(imageListPtr, shinfo.iIcon, ILD_TRANSPARENT);
}
return hIcon;
}
}