Skip to content

Commit

Permalink
Remove Custom Cache in ImageEx to evaluate binary impact size (#3736)
Browse files Browse the repository at this point in the history
Quick test to evaluate the footprint of this individual control on the package.

Based off PR #3727

Comparing against #3733, wondering if it's this single API or not that has an effect, as everything else seems rather common or using other things already being used in other APIs in the Toolkit...

Updated as we identified the custom caching in #2486 is the culprit to the application footprint. Reverting to just use System Cache with the applied fix still to evaluate final impact again with compiled dlls.

This should save around **675kb** to optimized app footprint.

Also Fixes #3741 removing unused element.
  • Loading branch information
msftbot[bot] authored Feb 17, 2021
2 parents 065ebe4 + d29e7b9 commit 189e91a
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Grid Margin="40"
Background="White">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Height="3000">
<Grid Height="6000">
<Border Width="200"
Height="200"
HorizontalAlignment="Center"
Expand All @@ -35,7 +35,8 @@
VerticalAlignment="Top"
Background="Transparent"
BorderThickness="0"
Click="CloseButton_Click">
Click="CloseButton_Click"
Foreground="Black">
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,19 @@ private async void Load()

if (lazyLoadingControlHost != null)
{
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
// Allow this to act as a toggle.
if (lazyLoadingControlHost.Child == null)
{
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
}
else
{
lazyLoadingControlHost.Child = null;
}
}
});

SampleController.Current.RegisterNewCommand("Clear image cache", async (sender, args) =>
{
container?.Children?.Clear();
GC.Collect(); // Force GC to free file locks
await ImageCache.Instance.ClearAsync();
});
SampleController.Current.RegisterNewCommand("Remove images", (sender, args) => container?.Children?.Clear());

await LoadDataAsync();
}
Expand Down
23 changes: 0 additions & 23 deletions Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/CachingStrategy.cs

This file was deleted.

15 changes: 13 additions & 2 deletions Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/ImageEx.Members.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace Microsoft.Toolkit.Uwp.UI.Controls
{
Expand Down Expand Up @@ -33,7 +34,12 @@ public Thickness NineGrid
/// <inheritdoc/>
public override CompositionBrush GetAlphaMask()
{
return IsInitialized ? (Image as Image).GetAlphaMask() : null;
if (IsInitialized && Image is Image image)
{
return image.GetAlphaMask();
}

return null;
}

/// <summary>
Expand All @@ -42,7 +48,12 @@ public override CompositionBrush GetAlphaMask()
/// <returns>The image as a <see cref="CastingSource"/>.</returns>
public CastingSource GetAsCastingSource()
{
return IsInitialized ? (Image as Image).GetAsCastingSource() : null;
if (IsInitialized && Image is Image image)
{
return image.GetAsCastingSource();
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ public partial class ImageExBase
/// </summary>
public static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));

/// <summary>
/// Identifies the <see cref="CachingStrategy"/> dependency property.
/// </summary>
public static readonly DependencyProperty CachingStrategyProperty = DependencyProperty.Register(nameof(CachingStrategy), typeof(ImageExCachingStrategy), typeof(ImageExBase), new PropertyMetadata(ImageExCachingStrategy.Custom));

/// <summary>
/// Identifies the <see cref="EnableLazyLoading"/> dependency property.
/// </summary>
Expand Down Expand Up @@ -126,15 +121,6 @@ public bool IsCacheEnabled
set { SetValue(IsCacheEnabledProperty, value); }
}

/// <summary>
/// Gets or sets a value indicating how the <see cref="ImageEx"/> will be cached.
/// </summary>
public ImageExCachingStrategy CachingStrategy
{
get { return (ImageExCachingStrategy)GetValue(CachingStrategyProperty); }
set { SetValue(CachingStrategyProperty, value); }
}

/// <summary>
/// Gets or sets a value indicating whether gets or sets is lazy loading enable. (17763 or higher supported)
/// </summary>
Expand Down
175 changes: 91 additions & 84 deletions Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/ImageExBase.Source.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public partial class ImageExBase
/// </summary>
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));

private Uri _uri;
private bool _isHttpSource;
private CancellationTokenSource _tokenSource = null;
//// Used to track if we get a new request, so we can cancel any potential custom cache loading.
private CancellationTokenSource _tokenSource;

private object _lazyLoadingSource;

/// <summary>
Expand Down Expand Up @@ -66,19 +66,28 @@ private static bool IsHttpUri(Uri uri)
return uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https");
}

/// <summary>
/// Method to call to assign an <see cref="ImageSource"/> value to the underlying <see cref="Image"/> powering <see cref="ImageExBase"/>.
/// </summary>
/// <param name="source"><see cref="ImageSource"/> to assign to the image.</param>
private void AttachSource(ImageSource source)
{
var image = Image as Image;
var brush = Image as ImageBrush;

if (image != null)
// 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 Image image)
{
image.Source = source;
}
else if (brush != null)
else if (Image is ImageBrush brush)
{
brush.ImageSource = source;
}

if (source == null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
}
}

private async void SetSource(object source)
Expand All @@ -88,15 +97,14 @@ private async void SetSource(object source)
return;
}

this._tokenSource?.Cancel();
_tokenSource?.Cancel();

this._tokenSource = new CancellationTokenSource();
_tokenSource = new CancellationTokenSource();

AttachSource(null);

if (source == null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
return;
}

Expand All @@ -107,122 +115,121 @@ private async void SetSource(object source)
{
AttachSource(imageSource);

ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
VisualStateManager.GoToState(this, LoadedState, true);
return;
}

_uri = source as Uri;
if (_uri == null)
var uri = source as Uri;
if (uri == null)
{
var url = source as string ?? source.ToString();
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _uri))
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
{
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
VisualStateManager.GoToState(this, FailedState, true);
return;
}
}

_isHttpSource = IsHttpUri(_uri);
if (!_isHttpSource && !_uri.IsAbsoluteUri)
if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
{
_uri = new Uri("ms-appx:///" + _uri.OriginalString.TrimStart('/'));
uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
}

await LoadImageAsync(_uri);
try
{
await LoadImageAsync(uri, _tokenSource.Token);
}
catch (OperationCanceledException)
{
// nothing to do as cancellation has been requested.
}
catch (Exception e)
{
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
VisualStateManager.GoToState(this, FailedState, true);
}
}

private async Task LoadImageAsync(Uri imageUri)
private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
{
if (_uri != null)
if (imageUri != null)
{
if (IsCacheEnabled)
{
switch (CachingStrategy)
var img = await ProvideCachedResourceAsync(imageUri, token);

if (!_tokenSource.IsCancellationRequested)
{
case ImageExCachingStrategy.Custom when _isHttpSource:
await SetHttpSourceCustomCached(imageUri);
break;
case ImageExCachingStrategy.Custom:
case ImageExCachingStrategy.Internal:
default:
AttachSource(new BitmapImage(imageUri));
break;
// Only attach our image if we still have a valid request.
AttachSource(img);
}
}
else if (string.Equals(_uri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
{
var source = _uri.OriginalString;
var source = imageUri.OriginalString;
const string base64Head = "base64,";
var index = source.IndexOf(base64Head);
if (index >= 0)
{
var bytes = Convert.FromBase64String(source.Substring(index + base64Head.Length));
var bitmap = new BitmapImage();
AttachSource(bitmap);
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());

if (!_tokenSource.IsCancellationRequested)
{
AttachSource(bitmap);
}
}
}
else
{
AttachSource(new BitmapImage(_uri)
AttachSource(new BitmapImage(imageUri)
{
CreateOptions = BitmapCreateOptions.IgnoreImageCache
});
}
}
}

private async Task SetHttpSourceCustomCached(Uri imageUri)
/// <summary>
/// This method is provided in case a developer would like their own custom caching strategy for <see cref="ImageExBase"/>.
/// By default it uses the built-in UWP cache provided by <see cref="BitmapImage"/> and
/// the <see cref="Image"/> control itself. This method should return an <see cref="ImageSource"/>
/// value of the image specified by the provided uri parameter.
/// A <see cref="CancellationToken"/> is provided in case the current request is invalidated
/// (e.g. the container is recycled before the original image is loaded).
/// The Toolkit also has an image cache helper which can be used as well:
/// <see cref="CacheBase{T}.GetFromCacheAsync(Uri, bool, CancellationToken, List{KeyValuePair{string, object}})"/> in <see cref="ImageCache"/>.
/// </summary>
/// <example>
/// <code>
/// var propValues = new List&lt;KeyValuePair&lt;string, object>>();
///
/// if (DecodePixelHeight > 0)
/// {
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelHeight), DecodePixelHeight));
/// }
/// if (DecodePixelWidth > 0)
/// {
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelWidth), DecodePixelWidth));
/// }
/// if (propValues.Count > 0)
/// {
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelType), DecodePixelType));
/// }
///
/// // A token is provided here as well to cancel the request to the cache,
/// // if a new image is requested.
/// return await ImageCache.Instance.GetFromCacheAsync(imageUri, true, token, propValues);
/// </code>
/// </example>
/// <param name="imageUri"><see cref="Uri"/> of the image to load from the cache.</param>
/// <param name="token">A <see cref="CancellationToken"/> which is used to signal when the current request is outdated.</param>
/// <returns><see cref="Task"/></returns>
protected virtual Task<ImageSource> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{
try
{
var propValues = new List<KeyValuePair<string, object>>();

if (DecodePixelHeight > 0)
{
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelHeight), DecodePixelHeight));
}

if (DecodePixelWidth > 0)
{
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelWidth), DecodePixelWidth));
}

if (propValues.Count > 0)
{
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelType), DecodePixelType));
}

var img = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, _tokenSource.Token, propValues);

lock (LockObj)
{
// If you have many imageEx in a virtualized ListView for instance
// controls will be recycled and the uri will change while waiting for the previous one to load
if (_uri == imageUri)
{
AttachSource(img);
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
VisualStateManager.GoToState(this, LoadedState, true);
}
}
}
catch (OperationCanceledException)
{
// nothing to do as cancellation has been requested.
}
catch (Exception e)
{
lock (LockObj)
{
if (_uri == imageUri)
{
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
VisualStateManager.GoToState(this, FailedState, true);
}
}
}
// By default we just use the built-in UWP image cache provided within the Image control.
return Task.FromResult((ImageSource)new BitmapImage(imageUri));
}
}
}
Loading

0 comments on commit 189e91a

Please sign in to comment.