[Peek] Add unsupported file icon fallback (#23735)

* Refactor icon retrieval, refactor hbitmap to bitmap conversion, add icon fallback

* Add svg to assets in installer
This commit is contained in:
Samuel Chapleau
2023-02-02 09:54:08 -08:00
committed by GitHub
parent 8fe3d8a1d8
commit 07e9780420
10 changed files with 109 additions and 94 deletions

View File

@@ -128,7 +128,7 @@
<?define PeekFiles=CommunityToolkit.Mvvm.dll;Microsoft.InteractiveExperiences.Projection.dll;WinRT.Runtime.dll;Ijwhost.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;Powertoys.Interop.dll;Peek.Common.dll;Peek.FilePreviewer.dll;Powertoys.Peek.UI.dll;Powertoys.Peek.UI.exe;Powertoys.Peek.UI.deps.json;Powertoys.Peek.UI.runtimeconfig.json;resources.pri;System.CodeDom.dll;System.Drawing.Common.dll;System.Management.dll;WIC.dll;WinUIEx.dll;System.IO.Abstractions.dll;PowerToys.Settings.UI.Lib.dll?>
<?define PeekAssetsFiles=Icon.ico?>
<?define PeekAssetsFiles=Icon.ico;DefaultFileIcon.svg?>
<?define PowerRenameSparsePackageAssets=LargeTile.png;SmallTile.png;SplashScreen.png;Square150x150Logo.png;Square44x44Logo.png;storelogo.png;Wide310x150Logo.png?>

View File

@@ -2,7 +2,7 @@
<!-- Licensed under the MIT License. -->
<UserControl
x:Class="Peek.FilePreviewer.UnsupportedFilePreview"
x:Class="Peek.FilePreviewer.Controls.UnsupportedFilePreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

@@ -4,16 +4,16 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.UI.Xaml.Media;
using Peek.Common.Helpers;
namespace Peek.FilePreviewer
namespace Peek.FilePreviewer.Controls
{
[INotifyPropertyChanged]
public sealed partial class UnsupportedFilePreview : UserControl
{
[ObservableProperty]
private BitmapSource? iconPreview;
private ImageSource? iconPreview;
[ObservableProperty]
private string? fileName;

View File

@@ -20,9 +20,9 @@
<Image
x:Name="ImagePreview"
Source="{x:Bind BitmapPreviewer.Preview, Mode=OneWay}"
MaxHeight="{x:Bind BitmapPreviewer.MaxImageSize.Height, Mode=OneWay}"
MaxWidth="{x:Bind BitmapPreviewer.MaxImageSize.Width, Mode=OneWay}"
MaxHeight="{x:Bind BitmapPreviewer.MaxImageSize.Height, Mode=OneWay}"
Source="{x:Bind BitmapPreviewer.Preview, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ImageInfoTooltip, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(BitmapPreviewer, Previewer.State), Mode=OneWay}" />
@@ -34,7 +34,7 @@
Source="{x:Bind BrowserPreviewer.Preview, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(BrowserPreviewer, Previewer.State), Mode=OneWay, FallbackValue=Collapsed}" />
<local:UnsupportedFilePreview
<controls:UnsupportedFilePreview
x:Name="UnsupportedFilePreview"
DateModified="{x:Bind UnsupportedFilePreviewer.DateModified, Mode=OneWay}"
FileName="{x:Bind UnsupportedFilePreviewer.FileName, Mode=OneWay}"

View File

@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Media.Imaging;
using Peek.Common;
namespace Peek.FilePreviewer.Previewers.Helpers
{
public static class BitmapHelper
{
public static async Task<BitmapSource> GetBitmapFromHBitmapAsync(IntPtr hbitmap, bool isSupportingTransparency, CancellationToken cancellationToken)
{
try
{
var bitmap = System.Drawing.Image.FromHbitmap(hbitmap);
if (isSupportingTransparency)
{
bitmap.MakeTransparent();
}
var bitmapImage = new BitmapImage();
cancellationToken.ThrowIfCancellationRequested();
using (var stream = new MemoryStream())
{
bitmap.Save(stream, isSupportingTransparency ? ImageFormat.Png : ImageFormat.Bmp);
stream.Position = 0;
cancellationToken.ThrowIfCancellationRequested();
await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
}
return bitmapImage;
}
finally
{
// delete HBitmap to avoid memory leaks
NativeMethods.DeleteObject(hbitmap);
}
}
}
}

View File

@@ -5,6 +5,10 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Peek.Common;
using Peek.Common.Models;
@@ -15,29 +19,46 @@ namespace Peek.FilePreviewer.Previewers.Helpers
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
public static HResult GetIcon(string fileName, out IntPtr hbitmap)
public static async Task<ImageSource?> GetIconAsync(string fileName, CancellationToken cancellationToken)
{
Guid shellItem2Guid = new Guid(IShellItem2Guid);
int retCode = NativeMethods.SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out IShellItem nativeShellItem);
if (retCode != 0)
ImageSource? imageSource = null;
IShellItem? nativeShellItem = null;
try
{
throw Marshal.GetExceptionForHR(retCode)!;
Guid shellItem2Guid = new(IShellItem2Guid);
int retCode = NativeMethods.SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
if (retCode != 0)
{
throw Marshal.GetExceptionForHR(retCode)!;
}
NativeSize large = new NativeSize { Width = 256, Height = 256 };
var options = ThumbnailOptions.BiggerSizeOk | ThumbnailOptions.IconOnly;
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(large, options, out IntPtr hbitmap);
cancellationToken.ThrowIfCancellationRequested();
if (hr == HResult.Ok)
{
imageSource = await BitmapHelper.GetBitmapFromHBitmapAsync(hbitmap, true, cancellationToken);
}
else
{
var svgImageSource = new SvgImageSource(new Uri("ms-appx:///Assets/DefaultFileIcon.svg"));
imageSource = svgImageSource;
}
}
finally
{
if (nativeShellItem != null)
{
Marshal.ReleaseComObject(nativeShellItem);
}
}
NativeSize large = new NativeSize { Width = 256, Height = 256 };
var options = ThumbnailOptions.BiggerSizeOk | ThumbnailOptions.IconOnly;
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(large, options, out hbitmap);
if (hr != HResult.Ok)
{
// TODO: fallback to a generic icon
}
Marshal.ReleaseComObject(nativeShellItem);
return hr;
return imageSource;
}
}
}

View File

@@ -2,13 +2,13 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.UI.Xaml.Media;
namespace Peek.FilePreviewer.Previewers
{
public interface IUnsupportedFilePreviewer : IPreviewer
{
public BitmapSource? IconPreview { get; }
public ImageSource? IconPreview { get; }
public string? FileName { get; }

View File

@@ -14,6 +14,7 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging;
using Peek.Common;
using Peek.Common.Extensions;
using Peek.FilePreviewer.Previewers.Helpers;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.Storage;
@@ -153,7 +154,7 @@ namespace Peek.FilePreviewer.Previewers
await Dispatcher.RunOnUiThread(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap, cancellationToken);
var thumbnailBitmap = await BitmapHelper.GetBitmapFromHBitmapAsync(hbitmap, false, cancellationToken);
if (!IsFullImageLoaded && !IsHighQualityThumbnailLoaded)
{
Preview = thumbnailBitmap;
@@ -181,7 +182,7 @@ namespace Peek.FilePreviewer.Previewers
await Dispatcher.RunOnUiThread(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap, cancellationToken);
var thumbnailBitmap = await BitmapHelper.GetBitmapFromHBitmapAsync(hbitmap, false, cancellationToken);
if (!IsFullImageLoaded)
{
Preview = thumbnailBitmap;
@@ -229,32 +230,6 @@ namespace Peek.FilePreviewer.Previewers
return bitmap;
}
private static async Task<BitmapSource> GetBitmapFromHBitmapAsync(IntPtr hbitmap, CancellationToken cancellationToken)
{
try
{
var bitmap = System.Drawing.Image.FromHbitmap(hbitmap);
var bitmapImage = new BitmapImage();
cancellationToken.ThrowIfCancellationRequested();
using (var stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Bmp);
stream.Position = 0;
cancellationToken.ThrowIfCancellationRequested();
await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
}
return bitmapImage;
}
finally
{
// delete HBitmap to avoid memory leaks
NativeMethods.DeleteObject(hbitmap);
}
}
public static bool IsFileTypeSupported(string fileExt)
{
return _supportedFileTypes.Contains(fileExt);

View File

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Peek.Common;
using Peek.Common.Extensions;
@@ -26,7 +26,7 @@ namespace Peek.FilePreviewer.Previewers
public partial class UnsupportedFilePreviewer : ObservableObject, IUnsupportedFilePreviewer, IDisposable
{
[ObservableProperty]
private BitmapSource? iconPreview;
private ImageSource? iconPreview;
[ObservableProperty]
private string? fileName;
@@ -123,15 +123,11 @@ namespace Peek.FilePreviewer.Previewers
{
return TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
IconHelper.GetIcon(Path.GetFullPath(File.Path), out IntPtr hbitmap);
cancellationToken.ThrowIfCancellationRequested();
await Dispatcher.RunOnUiThread(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var iconBitmap = await GetBitmapFromHBitmapWithTransparencyAsync(hbitmap, cancellationToken);
var iconBitmap = await IconHelper.GetIconAsync(Path.GetFullPath(File.Path), cancellationToken);
IconPreview = iconBitmap;
});
});
@@ -175,35 +171,5 @@ namespace Peek.FilePreviewer.Previewers
return hasFailedLoadingIconPreview && hasFailedLoadingDisplayInfo;
}
// TODO: Move this to a common helper file and make transparency a parameter (ImagePrevier uses the same code)
private static async Task<BitmapSource> GetBitmapFromHBitmapWithTransparencyAsync(IntPtr hbitmap, CancellationToken cancellationToken)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
var bitmap = System.Drawing.Image.FromHbitmap(hbitmap);
bitmap.MakeTransparent();
var bitmapImage = new BitmapImage();
cancellationToken.ThrowIfCancellationRequested();
using (var stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
stream.Position = 0;
cancellationToken.ThrowIfCancellationRequested();
await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
}
return bitmapImage;
}
finally
{
// delete HBitmap to avoid memory leaks
NativeMethods.DeleteObject(hbitmap);
}
}
}
}

View File

@@ -0,0 +1,5 @@
<svg width="auto" height="auto" viewBox="0 0 357 357" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M72.5156 323.531H284.484C287.561 323.531 290.062 321.029 290.062 317.953V100.406H239.859C230.632 100.406 223.125 92.8995 223.125 83.6719V33.4688H72.5156C69.4393 33.4688 66.9375 35.9705 66.9375 39.0469V317.953C66.9375 321.029 69.4393 323.531 72.5156 323.531Z" fill="white"/>
<path d="M284.964 89.25L234.281 38.5685V83.6719C234.281 86.7482 236.783 89.25 239.859 89.25H284.964Z" fill="white"/>
<path opacity="0.64" fill-rule="evenodd" clip-rule="evenodd" d="M301.219 94.8281C301.215 94.0033 301.026 93.1899 300.667 92.4476C299.925 89.5635 298.423 86.9315 296.317 84.8265L238.705 27.2156C235.574 24.0657 231.312 22.3 226.871 22.3124H72.5156C63.288 22.3124 55.7812 29.8192 55.7812 39.0468V317.953C55.7812 327.181 63.288 334.687 72.5156 334.687H284.484C293.712 334.687 301.219 327.181 301.219 317.953V96.6605C301.219 96.2629 301.191 95.8704 301.163 95.4781L301.159 95.4249C301.165 95.3614 301.174 95.2985 301.182 95.2357C301.201 95.1015 301.219 94.9677 301.219 94.8281ZM284.964 89.25H239.859C236.783 89.25 234.281 86.7482 234.281 83.6718V38.5685L284.964 89.25ZM72.5156 323.531H284.484C287.561 323.531 290.062 321.029 290.062 317.953V100.406H239.859C230.632 100.406 223.125 92.8995 223.125 83.6718V33.4687H72.5156C69.4393 33.4687 66.9375 35.9705 66.9375 39.0468V317.953C66.9375 321.029 69.4393 323.531 72.5156 323.531Z" fill="#605E5C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB