From dc56fb817bcee08b2aa318a45cb42380a836c7c7 Mon Sep 17 00:00:00 2001 From: Robson Date: Wed, 7 Dec 2022 18:14:52 -0800 Subject: [PATCH 1/5] [Peek] added extra logic to render PNG files with proper transparency --- .../ImagePreviewer/ImagePreviewer.cs | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs index 5474600806fd..e3c8e72649b7 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs @@ -12,10 +12,11 @@ namespace Peek.FilePreviewer.Previewers using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Dispatching; - using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Imaging; using Peek.Common; using Windows.Foundation; + using Windows.Graphics.Imaging; + using Windows.Storage.Streams; using File = Peek.Common.Models.File; public partial class ImagePreviewer : ObservableObject, IBitmapPreviewer, IDisposable @@ -63,6 +64,14 @@ public async Task GetPreviewSizeAsync() public Task LoadPreviewAsync() { + if (this.File.Extension == ".png") + { + var pngLowTask = LoadLowQualityPngAsync(); + var pngFullTask = LoadFullQualityPngAsync(); + + return Task.WhenAll(pngLowTask, pngFullTask); + } + var lowQualityThumbnailTask = LoadLowQualityThumbnailAsync(); var highQualityThumbnailTask = LoadHighQualityThumbnailAsync(); var fullImageTask = LoadFullQualityImageAsync(); @@ -70,6 +79,80 @@ public Task LoadPreviewAsync() return Task.WhenAll(lowQualityThumbnailTask, highQualityThumbnailTask, fullImageTask); } + private Task LoadLowQualityPngAsync() + { + var thumbnailTCS = new TaskCompletionSource(); + + Dispatcher.TryEnqueue(async () => + { + if (CancellationToken.IsCancellationRequested) + { + _cancellationTokenSource = new CancellationTokenSource(); + return; + } + + if (!IsFullImageLoaded && !IsHighQualityThumbnailLoaded) + { + // TODO: Handle thumbnail errors + // Open a Stream and decode a PNG image + Stream stream = new FileStream(File.Path, FileMode.Open, FileAccess.Read, FileShare.Read); + IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); + + // Create an encoder with the desired format + var encoder = await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, randomAccessStream); + + // preview image + var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(File.Path); + var imageStream = await file.GetThumbnailAsync( + Windows.Storage.FileProperties.ThumbnailMode.SingleItem, + 1280, + Windows.Storage.FileProperties.ThumbnailOptions.None); + + BitmapImage bitmapImage = new BitmapImage(); + bitmapImage.SetSource(imageStream); + + Preview = bitmapImage; + } + + thumbnailTCS.SetResult(); + }); + + return thumbnailTCS.Task; + } + + private Task LoadFullQualityPngAsync() + { + var thumbnailTCS = new TaskCompletionSource(); + Dispatcher.TryEnqueue(async () => + { + if (CancellationToken.IsCancellationRequested) + { + _cancellationTokenSource = new CancellationTokenSource(); + return; + } + + // TODO: Handle thumbnail errors + Stream stream = new FileStream(File.Path, FileMode.Open, FileAccess.Read, FileShare.Read); + IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); + + // Create an encoder with the desired format + var encoder = await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, randomAccessStream); + + // full quality image + var softwareBitmap = await encoder.GetSoftwareBitmapAsync(); + WriteableBitmap bitmap = new WriteableBitmap(softwareBitmap.PixelWidth, softwareBitmap.PixelHeight); + softwareBitmap.CopyToBuffer(bitmap.PixelBuffer); + + IsFullImageLoaded = true; + + Preview = bitmap; + + thumbnailTCS.SetResult(); + }); + + return thumbnailTCS.Task; + } + private Task LoadLowQualityThumbnailAsync() { var thumbnailTCS = new TaskCompletionSource(); From ccbb709ea040ebbfffdf5c1d6ea2552a6143c4b3 Mon Sep 17 00:00:00 2001 From: Robson Date: Thu, 8 Dec 2022 01:28:22 -0800 Subject: [PATCH 2/5] Moved logic to ThumbnailHelper Cleanup --- .../ImagePreviewer/Helpers/ThumbnailHelper.cs | 18 +++ .../ImagePreviewer/ImagePreviewer.cs | 153 +++++++----------- 2 files changed, 78 insertions(+), 93 deletions(-) diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs index cfa2631c8dd4..8ec58e596a12 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs @@ -7,6 +7,8 @@ namespace Peek.FilePreviewer.Previewers using System; using System.Collections.Generic; using System.Runtime.InteropServices; + using System.Threading.Tasks; + using Microsoft.UI.Xaml.Media.Imaging; using Peek.Common; using Peek.Common.Models; @@ -59,5 +61,21 @@ public static HResult GetThumbnail(string filename, out IntPtr hbitmap, NativeSi return hr; } + + public static async Task GetThumbnailAsync(string path, uint size) + { + // preview image + var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(path); + + var imageStream = await file.GetThumbnailAsync( + Windows.Storage.FileProperties.ThumbnailMode.SingleItem, + size, + Windows.Storage.FileProperties.ThumbnailOptions.None); + + BitmapImage bitmapImage = new BitmapImage(); + bitmapImage.SetSource(imageStream); + + return bitmapImage; + } } } diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs index e3c8e72649b7..944b5dbf8c18 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs @@ -64,14 +64,6 @@ public async Task GetPreviewSizeAsync() public Task LoadPreviewAsync() { - if (this.File.Extension == ".png") - { - var pngLowTask = LoadLowQualityPngAsync(); - var pngFullTask = LoadFullQualityPngAsync(); - - return Task.WhenAll(pngLowTask, pngFullTask); - } - var lowQualityThumbnailTask = LoadLowQualityThumbnailAsync(); var highQualityThumbnailTask = LoadHighQualityThumbnailAsync(); var fullImageTask = LoadFullQualityImageAsync(); @@ -79,80 +71,6 @@ public Task LoadPreviewAsync() return Task.WhenAll(lowQualityThumbnailTask, highQualityThumbnailTask, fullImageTask); } - private Task LoadLowQualityPngAsync() - { - var thumbnailTCS = new TaskCompletionSource(); - - Dispatcher.TryEnqueue(async () => - { - if (CancellationToken.IsCancellationRequested) - { - _cancellationTokenSource = new CancellationTokenSource(); - return; - } - - if (!IsFullImageLoaded && !IsHighQualityThumbnailLoaded) - { - // TODO: Handle thumbnail errors - // Open a Stream and decode a PNG image - Stream stream = new FileStream(File.Path, FileMode.Open, FileAccess.Read, FileShare.Read); - IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); - - // Create an encoder with the desired format - var encoder = await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, randomAccessStream); - - // preview image - var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(File.Path); - var imageStream = await file.GetThumbnailAsync( - Windows.Storage.FileProperties.ThumbnailMode.SingleItem, - 1280, - Windows.Storage.FileProperties.ThumbnailOptions.None); - - BitmapImage bitmapImage = new BitmapImage(); - bitmapImage.SetSource(imageStream); - - Preview = bitmapImage; - } - - thumbnailTCS.SetResult(); - }); - - return thumbnailTCS.Task; - } - - private Task LoadFullQualityPngAsync() - { - var thumbnailTCS = new TaskCompletionSource(); - Dispatcher.TryEnqueue(async () => - { - if (CancellationToken.IsCancellationRequested) - { - _cancellationTokenSource = new CancellationTokenSource(); - return; - } - - // TODO: Handle thumbnail errors - Stream stream = new FileStream(File.Path, FileMode.Open, FileAccess.Read, FileShare.Read); - IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); - - // Create an encoder with the desired format - var encoder = await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, randomAccessStream); - - // full quality image - var softwareBitmap = await encoder.GetSoftwareBitmapAsync(); - WriteableBitmap bitmap = new WriteableBitmap(softwareBitmap.PixelWidth, softwareBitmap.PixelHeight); - softwareBitmap.CopyToBuffer(bitmap.PixelBuffer); - - IsFullImageLoaded = true; - - Preview = bitmap; - - thumbnailTCS.SetResult(); - }); - - return thumbnailTCS.Task; - } - private Task LoadLowQualityThumbnailAsync() { var thumbnailTCS = new TaskCompletionSource(); @@ -166,10 +84,18 @@ private Task LoadLowQualityThumbnailAsync() if (!IsFullImageLoaded && !IsHighQualityThumbnailLoaded) { - // TODO: Handle thumbnail errors - ThumbnailHelper.GetThumbnail(Path.GetFullPath(File.Path), out IntPtr hbitmap, ThumbnailHelper.LowQualityThumbnailSize); - var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap); - Preview = thumbnailBitmap; + if (File.Extension == ".png") + { + // TODO: special condition for PNG for faster rendering using WinRT APIs + Preview = await ThumbnailHelper.GetThumbnailAsync(File.Path, 256); + } + else + { + // TODO: Handle thumbnail errors + ThumbnailHelper.GetThumbnail(Path.GetFullPath(File.Path), out IntPtr hbitmap, ThumbnailHelper.LowQualityThumbnailSize); + var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap); + Preview = thumbnailBitmap; + } } thumbnailTCS.SetResult(); @@ -191,11 +117,18 @@ private Task LoadHighQualityThumbnailAsync() if (!IsFullImageLoaded) { - // TODO: Handle thumbnail errors - ThumbnailHelper.GetThumbnail(Path.GetFullPath(File.Path), out IntPtr hbitmap, ThumbnailHelper.HighQualityThumbnailSize); - var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap); - IsHighQualityThumbnailLoaded = true; - Preview = thumbnailBitmap; + if (File.Extension == ".png") + { + Preview = await ThumbnailHelper.GetThumbnailAsync(File.Path, 720); + } + else + { + // TODO: Handle thumbnail errors + ThumbnailHelper.GetThumbnail(Path.GetFullPath(File.Path), out IntPtr hbitmap, ThumbnailHelper.HighQualityThumbnailSize); + var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap); + IsHighQualityThumbnailLoaded = true; + Preview = thumbnailBitmap; + } } thumbnailTCS.SetResult(); @@ -209,8 +142,18 @@ private Task LoadFullQualityImageAsync() var fullImageTCS = new TaskCompletionSource(); Dispatcher.TryEnqueue(async () => { - // TODO: Check if this is performant - var bitmap = await GetFullBitmapFromPathAsync(File.Path); + BitmapSource bitmap; + + if (File.Extension == ".png") + { + bitmap = await GetDecodedImageAsync(File.Path, File.Extension); + } + else + { + // TODO: Check if this is performant + bitmap = await GetFullBitmapFromPathAsync(File.Path); + } + IsFullImageLoaded = true; if (CancellationToken.IsCancellationRequested) @@ -259,6 +202,30 @@ private static async Task GetBitmapFromHBitmapAsync(IntPtr hbitmap } } + private static async Task GetDecodedImageAsync(string path, string extension) + { + Guid decoderId = BitmapDecoder.JpegDecoderId; + + // Check extension and auto select decoder id + if (extension == ".png") + { + decoderId = BitmapDecoder.PngDecoderId; + } + + // TODO: consider StorageFile instead + Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); + + // Create an encoder with the desired format + var encoder = await BitmapDecoder.CreateAsync(decoderId, randomAccessStream); + var softwareBitmap = await encoder.GetSoftwareBitmapAsync(); + + var wBitmap = new WriteableBitmap(softwareBitmap.PixelWidth, softwareBitmap.PixelHeight); + softwareBitmap.CopyToBuffer(wBitmap.PixelBuffer); + + return wBitmap; + } + public static bool IsFileTypeSupported(string fileExt) { return _supportedFileTypes.Contains(fileExt); From 20f1b8dd79098b586776c3305eaa297fc0a54bca Mon Sep 17 00:00:00 2001 From: Robson Date: Thu, 8 Dec 2022 14:39:08 -0800 Subject: [PATCH 3/5] Created a separated previewer for PNG to only load the preview image with thumbnail logic --- .../Previewers/PngPreviewer/PngPreviewer.cs | 94 +++++++++++++++++++ .../Previewers/PreviewerFactory.cs | 6 +- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs new file mode 100644 index 000000000000..335bb5996450 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs @@ -0,0 +1,94 @@ +// 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. + +namespace Peek.FilePreviewer.Previewers +{ + using System; + using System.Collections.Generic; + using System.Drawing.Imaging; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using CommunityToolkit.Mvvm.ComponentModel; + using Microsoft.UI.Dispatching; + using Microsoft.UI.Xaml.Media.Imaging; + using Peek.Common; + using Windows.Foundation; + using Windows.Graphics.Imaging; + using Windows.Storage.Streams; + using File = Peek.Common.Models.File; + + public partial class PngPreviewer : ObservableObject, IBitmapPreviewer + { + private readonly uint _png_image_size = 1280; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsPreviewLoaded))] + private BitmapSource? preview; + + public PngPreviewer(File file) + { + File = file; + Dispatcher = DispatcherQueue.GetForCurrentThread(); + } + + public bool IsPreviewLoaded => preview != null; + + private File File { get; } + + private DispatcherQueue Dispatcher { get; } + + private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private CancellationToken CancellationToken => _cancellationTokenSource.Token; + + public void Dispose() + { + _cancellationTokenSource.Dispose(); + GC.SuppressFinalize(this); + } + + public async Task GetPreviewSizeAsync() + { + var propertyImageSize = await PropertyHelper.GetImageSize(File.Path); + if (propertyImageSize != Size.Empty) + { + return propertyImageSize; + } + + return await WICHelper.GetImageSize(File.Path); + } + + public Task LoadPreviewAsync() + { + var previewTask = LoadPreviewImageAsync(); + + return Task.WhenAll(previewTask); + } + + public static bool IsFileTypeSupported(string fileExt) + { + return fileExt == ".png" ? true : false; + } + + private Task LoadPreviewImageAsync() + { + var thumbnailTCS = new TaskCompletionSource(); + Dispatcher.TryEnqueue(async () => + { + if (CancellationToken.IsCancellationRequested) + { + _cancellationTokenSource = new CancellationTokenSource(); + return; + } + + Preview = await ThumbnailHelper.GetThumbnailAsync(File.Path, _png_image_size); + + thumbnailTCS.SetResult(); + }); + + return thumbnailTCS.Task; + } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs index 36651e851123..4f8c09fa4fa0 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs @@ -10,7 +10,11 @@ public class PreviewerFactory { public IPreviewer? Create(File file) { - if (ImagePreviewer.IsFileTypeSupported(file.Extension)) + if (PngPreviewer.IsFileTypeSupported(file.Extension)) + { + return new PngPreviewer(file); + } + else if (ImagePreviewer.IsFileTypeSupported(file.Extension)) { return new ImagePreviewer(file); } From db7691724c24a4da75989a67523fbaa86c6bf405 Mon Sep 17 00:00:00 2001 From: Robson Date: Thu, 8 Dec 2022 14:44:03 -0800 Subject: [PATCH 4/5] removed unused code --- .../ImagePreviewer/ImagePreviewer.cs | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs index 245c460487e4..a6b4f0b091bb 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/ImagePreviewer.cs @@ -17,8 +17,6 @@ namespace Peek.FilePreviewer.Previewers using Peek.Common; using Peek.Common.Extensions; using Windows.Foundation; - using Windows.Graphics.Imaging; - using Windows.Storage.Streams; using File = Peek.Common.Models.File; public partial class ImagePreviewer : ObservableObject, IBitmapPreviewer, IDisposable @@ -218,30 +216,6 @@ private static async Task GetBitmapFromHBitmapAsync(IntPtr hbitmap } } - private static async Task GetDecodedImageAsync(string path, string extension) - { - Guid decoderId = BitmapDecoder.JpegDecoderId; - - // Check extension and auto select decoder id - if (extension == ".png") - { - decoderId = BitmapDecoder.PngDecoderId; - } - - // TODO: consider StorageFile instead - Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); - IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); - - // Create an encoder with the desired format - var encoder = await BitmapDecoder.CreateAsync(decoderId, randomAccessStream); - var softwareBitmap = await encoder.GetSoftwareBitmapAsync(); - - var wBitmap = new WriteableBitmap(softwareBitmap.PixelWidth, softwareBitmap.PixelHeight); - softwareBitmap.CopyToBuffer(wBitmap.PixelBuffer); - - return wBitmap; - } - public static bool IsFileTypeSupported(string fileExt) { return _supportedFileTypes.Contains(fileExt); @@ -258,7 +232,8 @@ public static bool IsFileTypeSupported(string fileExt) ".jif", ".jpeg", ".jpe", - ".png", + + // ".png", // The current ImagePreviewer logic does not support transparency so PNG has it's own logic in PngPreviewer ".tif", ".tiff", ".dib", From 6726ebd042d4c645fe13b8ee88f6b4ec22e26a71 Mon Sep 17 00:00:00 2001 From: Robson Date: Thu, 8 Dec 2022 14:58:26 -0800 Subject: [PATCH 5/5] Updated state loading change --- .../ImagePreviewer/Helpers/ThumbnailHelper.cs | 15 +++++++++-- .../Previewers/PngPreviewer/PngPreviewer.cs | 27 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs index 8ec58e596a12..8304369dbe4a 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/ThumbnailHelper.cs @@ -62,17 +62,28 @@ public static HResult GetThumbnail(string filename, out IntPtr hbitmap, NativeSi return hr; } - public static async Task GetThumbnailAsync(string path, uint size) + public static async Task GetThumbnailAsync(string path, uint size) { + BitmapImage? bitmapImage = null; + // preview image var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(path); + if (file == null) + { + return bitmapImage; + } var imageStream = await file.GetThumbnailAsync( Windows.Storage.FileProperties.ThumbnailMode.SingleItem, size, Windows.Storage.FileProperties.ThumbnailOptions.None); - BitmapImage bitmapImage = new BitmapImage(); + if (imageStream == null) + { + return bitmapImage; + } + + bitmapImage = new BitmapImage(); bitmapImage.SetSource(imageStream); return bitmapImage; diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs index 335bb5996450..213e67120a19 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PngPreviewer/PngPreviewer.cs @@ -27,10 +27,15 @@ public partial class PngPreviewer : ObservableObject, IBitmapPreviewer [NotifyPropertyChangedFor(nameof(IsPreviewLoaded))] private BitmapSource? preview; + [ObservableProperty] + private PreviewState state; + public PngPreviewer(File file) { File = file; Dispatcher = DispatcherQueue.GetForCurrentThread(); + + PropertyChanged += OnPropertyChanged; } public bool IsPreviewLoaded => preview != null; @@ -60,11 +65,18 @@ public async Task GetPreviewSizeAsync() return await WICHelper.GetImageSize(File.Path); } - public Task LoadPreviewAsync() + public async Task LoadPreviewAsync() { + State = PreviewState.Loading; + var previewTask = LoadPreviewImageAsync(); - return Task.WhenAll(previewTask); + await Task.WhenAll(previewTask); + + if (Preview == null) + { + State = PreviewState.Error; + } } public static bool IsFileTypeSupported(string fileExt) @@ -72,6 +84,17 @@ public static bool IsFileTypeSupported(string fileExt) return fileExt == ".png" ? true : false; } + private void OnPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Preview)) + { + if (Preview != null) + { + State = PreviewState.Loaded; + } + } + } + private Task LoadPreviewImageAsync() { var thumbnailTCS = new TaskCompletionSource();