mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor cached image
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
protected override async Task<Uri?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
IImageCache imageCache = this.ServiceProvider().GetRequiredService<IImageCache>();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="LazyLoadingThreshold" Value="256"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="shci:CachedImage">
|
||||
|
||||
@@ -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<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
protected virtual Task<Uri?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
// By default we just use the built-in UWP image cache provided within the Image control.
|
||||
return Task.FromResult<ImageSource?>(new BitmapImage(imageUri));
|
||||
return Task.FromResult<Uri?>(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<FrameworkElement> ascendants = this.FindAscendants().OfType<FrameworkElement>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,14 @@
|
||||
<Grid>
|
||||
<Grid CornerRadius="{StaticResource ControlCornerRadius}">
|
||||
<!-- Disable some CachedImage's LazyLoading function here can increase response speed -->
|
||||
<shci:CachedImage EnableLazyLoading="False" Source="{x:Bind Quality, Converter={StaticResource QualityConverter}, Mode=OneWay}"/>
|
||||
<shci:CachedImage EnableLazyLoading="False" Source="{StaticResource UI_ImgSign_ItemIcon}"/>
|
||||
<shci:CachedImage Source="{x:Bind Quality, Converter={StaticResource QualityConverter}, Mode=OneWay}"/>
|
||||
<shci:CachedImage Source="{StaticResource UI_ImgSign_ItemIcon}"/>
|
||||
<shci:CachedImage Source="{x:Bind Icon, Mode=OneWay}"/>
|
||||
<shci:CachedImage
|
||||
Margin="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
shch:FrameworkElementHelper.SquareLength="16"
|
||||
EnableLazyLoading="False"
|
||||
Source="{x:Bind Badge, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<shci:CachedImage
|
||||
Width="120"
|
||||
Height="120"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon272}"/>
|
||||
<TextBlock
|
||||
Margin="0,16,0,0"
|
||||
|
||||
@@ -251,7 +251,6 @@
|
||||
<shci:CachedImage
|
||||
Height="120"
|
||||
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon52}"/>
|
||||
<TextBlock
|
||||
Margin="0,5,0,21"
|
||||
|
||||
@@ -415,7 +415,6 @@
|
||||
<shci:CachedImage
|
||||
Height="120"
|
||||
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon445}"/>
|
||||
<TextBlock
|
||||
Margin="0,5,0,21"
|
||||
|
||||
@@ -369,7 +369,6 @@
|
||||
<shci:CachedImage
|
||||
Height="120"
|
||||
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon89}"/>
|
||||
<TextBlock
|
||||
Margin="0,5,0,21"
|
||||
@@ -754,7 +753,6 @@
|
||||
<shci:CachedImage
|
||||
Height="120"
|
||||
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon89}"/>
|
||||
<TextBlock
|
||||
Margin="0,5,0,21"
|
||||
|
||||
@@ -238,7 +238,6 @@
|
||||
<shci:CachedImage
|
||||
Height="120"
|
||||
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon89}"/>
|
||||
<TextBlock
|
||||
Margin="0,5,0,21"
|
||||
@@ -365,7 +364,6 @@
|
||||
<shci:CachedImage
|
||||
Height="120"
|
||||
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
|
||||
EnableLazyLoading="False"
|
||||
Source="{StaticResource UI_EmotionIcon89}"/>
|
||||
<TextBlock
|
||||
Margin="0,5,0,21"
|
||||
|
||||
Reference in New Issue
Block a user