From f4593cd325fa2999bdfb5b33795a8e2ed50a6679 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Wed, 15 May 2024 13:39:19 +0800 Subject: [PATCH] refactor cached image --- .../Snap.Hutao/Control/Image/CachedImage.cs | 10 +- .../Snap.Hutao/Control/Image/CachedImage.xaml | 1 - .../Image/Implementation/ImageExBase.cs | 196 ++---------------- .../Snap.Hutao/View/Control/ItemIcon.xaml | 5 +- .../Snap.Hutao/View/Control/LoadingView.xaml | 1 - .../Snap.Hutao/View/Page/FeedbackPage.xaml | 1 - .../Snap.Hutao/View/Page/LaunchGamePage.xaml | 1 - .../Snap.Hutao/View/Page/WikiAvatarPage.xaml | 2 - .../Snap.Hutao/View/Page/WikiWeaponPage.xaml | 2 - 9 files changed, 26 insertions(+), 193 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs index 94792415..29bf5913 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs @@ -23,13 +23,10 @@ internal sealed class CachedImage : Implementation.ImageEx { DefaultStyleKey = typeof(CachedImage); DefaultStyleResourceUri = "ms-appx:///Control/Image/CachedImage.xaml".ToUri(); - - IsCacheEnabled = true; - EnableLazyLoading = false; } /// - protected override async Task ProvideCachedResourceAsync(Uri imageUri, CancellationToken token) + protected override async Task ProvideCachedResourceAsync(Uri imageUri, CancellationToken token) { IImageCache imageCache = this.ServiceProvider().GetRequiredService(); @@ -38,10 +35,7 @@ internal sealed class CachedImage : Implementation.ImageEx HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri); string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread. token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled. - - // https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-animations-and-media#optimize-image-resources - // BitmapImage initialize with a uri will increase image quality and loading speed. - return new BitmapImage(file.ToUri()); + return file.ToUri(); } catch (COMException) { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.xaml index f74b45dd..372a7f5c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.xaml @@ -6,7 +6,6 @@ - diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs index ee41e58f..b4cf793b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs @@ -21,12 +21,6 @@ namespace Snap.Hutao.Control.Image.Implementation; [TemplatePart(Name = PartImage, Type = typeof(object))] [TemplatePart(Name = PartPlaceholderImage, Type = typeof(object))] [DependencyProperty("Stretch", typeof(Stretch), Stretch.Uniform)] -[DependencyProperty("DecodePixelHeight", typeof(int), 0)] -[DependencyProperty("DecodePixelWidth", typeof(int), 0)] -[DependencyProperty("DecodePixelType", typeof(DecodePixelType), DecodePixelType.Physical)] -[DependencyProperty("IsCacheEnabled", typeof(bool), false)] -[DependencyProperty("EnableLazyLoading", typeof(bool), false, nameof(EnableLazyLoadingChanged))] -[DependencyProperty("LazyLoadingThreshold", typeof(double), default(double), nameof(LazyLoadingThresholdChanged))] [DependencyProperty("PlaceholderSource", typeof(object), default(object))] [DependencyProperty("PlaceholderStretch", typeof(Stretch), Stretch.Uniform)] [DependencyProperty("PlaceholderMargin", typeof(Thickness))] @@ -42,8 +36,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control protected const string FailedState = "Failed"; private CancellationTokenSource? tokenSource; - private object? lazyLoadingSource; - private bool isInViewport; public bool IsInitialized { get; private set; } @@ -58,10 +50,10 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control public abstract CompositionBrush GetAlphaMask(); - protected virtual Task ProvideCachedResourceAsync(Uri imageUri, CancellationToken token) + protected virtual Task ProvideCachedResourceAsync(Uri imageUri, CancellationToken token) { // By default we just use the built-in UWP image cache provided within the Image control. - return Task.FromResult(new BitmapImage(imageUri)); + return Task.FromResult(imageUri); } protected virtual void OnImageOpened(object sender, RoutedEventArgs e) @@ -80,19 +72,10 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control RemoveImageFailed(OnImageFailed); Image = GetTemplateChild(PartImage); - PlaceholderImage = GetTemplateChild(PartPlaceholderImage); IsInitialized = true; - if (Source is null || !EnableLazyLoading || isInViewport) - { - lazyLoadingSource = null; - SetSource(Source); - } - else - { - lazyLoadingSource = Source; - } + SetSource(Source); AttachImageOpened(OnImageOpened); AttachImageFailed(OnImageFailed); @@ -148,33 +131,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control } } - private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not ImageExBase control) - { - return; - } - - bool value = (bool)e.NewValue; - if (value) - { - control.LayoutUpdated += control.OnImageExBaseLayoutUpdated; - control.InvalidateLazyLoading(); - } - else - { - control.LayoutUpdated -= control.OnImageExBaseLayoutUpdated; - } - } - - private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is ImageExBase { EnableLazyLoading: true } control) - { - control.InvalidateLazyLoading(); - } - } - private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not ImageExBase control) @@ -187,15 +143,7 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control return; } - if (e.NewValue is null || !control.EnableLazyLoading || control.isInViewport) - { - control.lazyLoadingSource = null; - control.SetSource(e.NewValue); - } - else - { - control.lazyLoadingSource = e.NewValue; - } + control.SetSource(e.NewValue); } private static bool IsHttpUri(Uri uri) @@ -203,11 +151,8 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control return uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https"); } - private void AttachSource(ImageSource? source) + private void AttachSource(BitmapImage? source, Uri? uri) { - // Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState - // as we register to both the ImageOpened/ImageFailed events of the underlying control. - // We only need to call those methods if we fail in other cases before we get here. if (Image is Microsoft.UI.Xaml.Controls.Image image) { image.Source = source; @@ -221,13 +166,15 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control { VisualStateManager.GoToState(this, UnloadedState, true); } - else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 }) + else { + // https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-animations-and-media#optimize-image-resources + source.UriSource = uri; VisualStateManager.GoToState(this, LoadedState, true); } } - private void AttachPlaceholderSource(ImageSource? source) + private void AttachPlaceholderSource(BitmapImage? source, Uri? uri) { if (PlaceholderImage is Microsoft.UI.Xaml.Controls.Image image) { @@ -242,8 +189,10 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control { VisualStateManager.GoToState(this, UnloadedState, true); } - else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 }) + else { + // https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-animations-and-media#optimize-image-resources + source.UriSource = uri; VisualStateManager.GoToState(this, LoadedState, true); } } @@ -256,10 +205,9 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control } tokenSource?.Cancel(); - tokenSource = new CancellationTokenSource(); - AttachSource(null); + AttachSource(default, default); if (source is null) { @@ -268,13 +216,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control VisualStateManager.GoToState(this, LoadingState, true); - if (source as ImageSource is { } imageSource) - { - AttachSource(imageSource); - - return; - } - if (source as Uri is not { } uri) { string? url = source as string ?? source.ToString(); @@ -319,20 +260,13 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control tokenSource?.Cancel(); tokenSource = new(); - AttachPlaceholderSource(null); + AttachPlaceholderSource(default, default); if (source is null) { return; } - if (source as ImageSource is { } imageSource) - { - AttachPlaceholderSource(imageSource); - - return; - } - if (source as Uri is not { } uri) { string? url = source as string ?? source.ToString(); @@ -354,13 +288,13 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control return; } - ImageSource? img = await ProvideCachedResourceAsync(uri, tokenSource.Token).ConfigureAwait(true); + Uri? actualUri = await ProvideCachedResourceAsync(uri, tokenSource.Token).ConfigureAwait(true); ArgumentNullException.ThrowIfNull(tokenSource); if (!tokenSource.IsCancellationRequested) { // Only attach our image if we still have a valid request. - AttachPlaceholderSource(img); + AttachPlaceholderSource(new BitmapImage(), actualUri); } } catch (OperationCanceledException) @@ -379,99 +313,13 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control return; } - if (IsCacheEnabled) + Uri? actualUri = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true); + + ArgumentNullException.ThrowIfNull(tokenSource); + if (!tokenSource.IsCancellationRequested) { - ImageSource? img = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true); - - ArgumentNullException.ThrowIfNull(tokenSource); - if (!tokenSource.IsCancellationRequested) - { - // Only attach our image if we still have a valid request. - AttachSource(img); - } - } - else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase)) - { - string source = imageUri.OriginalString; - const string base64Head = "base64,"; - int index = source.IndexOf(base64Head, StringComparison.Ordinal); - if (index >= 0) - { - byte[] bytes = Convert.FromBase64String(source[(index + base64Head.Length)..]); - BitmapImage bitmap = new(); - await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream()); - - ArgumentNullException.ThrowIfNull(tokenSource); - if (!tokenSource.IsCancellationRequested) - { - AttachSource(bitmap); - } - } - } - else - { - AttachSource(new BitmapImage(imageUri) - { - CreateOptions = BitmapCreateOptions.IgnoreImageCache, - }); - } - } - - private void OnImageExBaseLayoutUpdated(object? sender, object e) - { - InvalidateLazyLoading(); - } - - private void InvalidateLazyLoading() - { - if (!IsLoaded) - { - isInViewport = false; - return; - } - - // Find the first ascendant ScrollViewer, if not found, use the root element. - FrameworkElement? hostElement = default; - IEnumerable ascendants = this.FindAscendants().OfType(); - foreach (FrameworkElement ascendant in ascendants) - { - hostElement = ascendant; - if (hostElement is Microsoft.UI.Xaml.Controls.ScrollViewer) - { - break; - } - } - - if (hostElement is null) - { - isInViewport = false; - return; - } - - Rect controlRect = TransformToVisual(hostElement).TransformBounds(StructMarshal.Rect(ActualSize)); - double lazyLoadingThreshold = LazyLoadingThreshold; - - // Left/Top 1 Threshold, Right/Bottom 2 Threshold - Rect hostRect = new( - 0 - lazyLoadingThreshold, - 0 - lazyLoadingThreshold, - hostElement.ActualWidth + (2 * lazyLoadingThreshold), - hostElement.ActualHeight + (2 * lazyLoadingThreshold)); - - if (controlRect.IntersectsWith(hostRect)) - { - isInViewport = true; - - if (lazyLoadingSource is not null) - { - object source = lazyLoadingSource; - lazyLoadingSource = null; - SetSource(source); - } - } - else - { - isInViewport = false; + // Only attach our image if we still have a valid request. + AttachSource(new BitmapImage(), actualUri); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml index 6a28e0de..1f71de94 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/ItemIcon.xaml @@ -15,15 +15,14 @@ - - + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml index a82a1f0d..a9934b3f 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml @@ -17,7 +17,6 @@