mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
[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:
@@ -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?>
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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}"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
src/modules/peek/Peek.UI/Assets/DefaultFileIcon.svg
Normal file
5
src/modules/peek/Peek.UI/Assets/DefaultFileIcon.svg
Normal 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 |
Reference in New Issue
Block a user