Skip to content

Commit 189e91a

Browse files
author
msftbot[bot]
authored
Remove Custom Cache in ImageEx to evaluate binary impact size (#3736)
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.
2 parents 065ebe4 + d29e7b9 commit 189e91a

File tree

8 files changed

+138
-186
lines changed

8 files changed

+138
-186
lines changed

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ImageEx/ImageExLazyLoadingControl.xaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<Grid Margin="40"
1212
Background="White">
1313
<ScrollViewer VerticalScrollBarVisibility="Auto">
14-
<Grid Height="3000">
14+
<Grid Height="6000">
1515
<Border Width="200"
1616
Height="200"
1717
HorizontalAlignment="Center"
@@ -35,7 +35,8 @@
3535
VerticalAlignment="Top"
3636
Background="Transparent"
3737
BorderThickness="0"
38-
Click="CloseButton_Click">
38+
Click="CloseButton_Click"
39+
Foreground="Black">
3940
<SymbolIcon Symbol="Cancel" />
4041
</Button>
4142
</Grid>

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ImageEx/ImageExPage.xaml.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,19 @@ private async void Load()
8282

8383
if (lazyLoadingControlHost != null)
8484
{
85-
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
85+
// Allow this to act as a toggle.
86+
if (lazyLoadingControlHost.Child == null)
87+
{
88+
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
89+
}
90+
else
91+
{
92+
lazyLoadingControlHost.Child = null;
93+
}
8694
}
8795
});
8896

89-
SampleController.Current.RegisterNewCommand("Clear image cache", async (sender, args) =>
90-
{
91-
container?.Children?.Clear();
92-
GC.Collect(); // Force GC to free file locks
93-
await ImageCache.Instance.ClearAsync();
94-
});
97+
SampleController.Current.RegisterNewCommand("Remove images", (sender, args) => container?.Children?.Clear());
9598

9699
await LoadDataAsync();
97100
}

Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/CachingStrategy.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.

Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/ImageEx.Members.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Windows.UI.Composition;
77
using Windows.UI.Xaml;
88
using Windows.UI.Xaml.Controls;
9+
using Windows.UI.Xaml.Media;
910

1011
namespace Microsoft.Toolkit.Uwp.UI.Controls
1112
{
@@ -33,7 +34,12 @@ public Thickness NineGrid
3334
/// <inheritdoc/>
3435
public override CompositionBrush GetAlphaMask()
3536
{
36-
return IsInitialized ? (Image as Image).GetAlphaMask() : null;
37+
if (IsInitialized && Image is Image image)
38+
{
39+
return image.GetAlphaMask();
40+
}
41+
42+
return null;
3743
}
3844

3945
/// <summary>
@@ -42,7 +48,12 @@ public override CompositionBrush GetAlphaMask()
4248
/// <returns>The image as a <see cref="CastingSource"/>.</returns>
4349
public CastingSource GetAsCastingSource()
4450
{
45-
return IsInitialized ? (Image as Image).GetAsCastingSource() : null;
51+
if (IsInitialized && Image is Image image)
52+
{
53+
return image.GetAsCastingSource();
54+
}
55+
56+
return null;
4657
}
4758
}
4859
}

Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/ImageExBase.Members.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ public partial class ImageExBase
4040
/// </summary>
4141
public static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
4242

43-
/// <summary>
44-
/// Identifies the <see cref="CachingStrategy"/> dependency property.
45-
/// </summary>
46-
public static readonly DependencyProperty CachingStrategyProperty = DependencyProperty.Register(nameof(CachingStrategy), typeof(ImageExCachingStrategy), typeof(ImageExBase), new PropertyMetadata(ImageExCachingStrategy.Custom));
47-
4843
/// <summary>
4944
/// Identifies the <see cref="EnableLazyLoading"/> dependency property.
5045
/// </summary>
@@ -126,15 +121,6 @@ public bool IsCacheEnabled
126121
set { SetValue(IsCacheEnabledProperty, value); }
127122
}
128123

129-
/// <summary>
130-
/// Gets or sets a value indicating how the <see cref="ImageEx"/> will be cached.
131-
/// </summary>
132-
public ImageExCachingStrategy CachingStrategy
133-
{
134-
get { return (ImageExCachingStrategy)GetValue(CachingStrategyProperty); }
135-
set { SetValue(CachingStrategyProperty, value); }
136-
}
137-
138124
/// <summary>
139125
/// Gets or sets a value indicating whether gets or sets is lazy loading enable. (17763 or higher supported)
140126
/// </summary>

Microsoft.Toolkit.Uwp.UI.Controls.Core/ImageEx/ImageExBase.Source.cs

Lines changed: 91 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public partial class ImageExBase
2424
/// </summary>
2525
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));
2626

27-
private Uri _uri;
28-
private bool _isHttpSource;
29-
private CancellationTokenSource _tokenSource = null;
27+
//// Used to track if we get a new request, so we can cancel any potential custom cache loading.
28+
private CancellationTokenSource _tokenSource;
29+
3030
private object _lazyLoadingSource;
3131

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

69+
/// <summary>
70+
/// Method to call to assign an <see cref="ImageSource"/> value to the underlying <see cref="Image"/> powering <see cref="ImageExBase"/>.
71+
/// </summary>
72+
/// <param name="source"><see cref="ImageSource"/> to assign to the image.</param>
6973
private void AttachSource(ImageSource source)
7074
{
71-
var image = Image as Image;
72-
var brush = Image as ImageBrush;
73-
74-
if (image != null)
75+
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
76+
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
77+
// We only need to call those methods if we fail in other cases before we get here.
78+
if (Image is Image image)
7579
{
7680
image.Source = source;
7781
}
78-
else if (brush != null)
82+
else if (Image is ImageBrush brush)
7983
{
8084
brush.ImageSource = source;
8185
}
86+
87+
if (source == null)
88+
{
89+
VisualStateManager.GoToState(this, UnloadedState, true);
90+
}
8291
}
8392

8493
private async void SetSource(object source)
@@ -88,15 +97,14 @@ private async void SetSource(object source)
8897
return;
8998
}
9099

91-
this._tokenSource?.Cancel();
100+
_tokenSource?.Cancel();
92101

93-
this._tokenSource = new CancellationTokenSource();
102+
_tokenSource = new CancellationTokenSource();
94103

95104
AttachSource(null);
96105

97106
if (source == null)
98107
{
99-
VisualStateManager.GoToState(this, UnloadedState, true);
100108
return;
101109
}
102110

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

110-
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
111-
VisualStateManager.GoToState(this, LoadedState, true);
112118
return;
113119
}
114120

115-
_uri = source as Uri;
116-
if (_uri == null)
121+
var uri = source as Uri;
122+
if (uri == null)
117123
{
118124
var url = source as string ?? source.ToString();
119-
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _uri))
125+
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
120126
{
127+
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
121128
VisualStateManager.GoToState(this, FailedState, true);
122129
return;
123130
}
124131
}
125132

126-
_isHttpSource = IsHttpUri(_uri);
127-
if (!_isHttpSource && !_uri.IsAbsoluteUri)
133+
if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
128134
{
129-
_uri = new Uri("ms-appx:///" + _uri.OriginalString.TrimStart('/'));
135+
uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
130136
}
131137

132-
await LoadImageAsync(_uri);
138+
try
139+
{
140+
await LoadImageAsync(uri, _tokenSource.Token);
141+
}
142+
catch (OperationCanceledException)
143+
{
144+
// nothing to do as cancellation has been requested.
145+
}
146+
catch (Exception e)
147+
{
148+
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
149+
VisualStateManager.GoToState(this, FailedState, true);
150+
}
133151
}
134152

135-
private async Task LoadImageAsync(Uri imageUri)
153+
private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
136154
{
137-
if (_uri != null)
155+
if (imageUri != null)
138156
{
139157
if (IsCacheEnabled)
140158
{
141-
switch (CachingStrategy)
159+
var img = await ProvideCachedResourceAsync(imageUri, token);
160+
161+
if (!_tokenSource.IsCancellationRequested)
142162
{
143-
case ImageExCachingStrategy.Custom when _isHttpSource:
144-
await SetHttpSourceCustomCached(imageUri);
145-
break;
146-
case ImageExCachingStrategy.Custom:
147-
case ImageExCachingStrategy.Internal:
148-
default:
149-
AttachSource(new BitmapImage(imageUri));
150-
break;
163+
// Only attach our image if we still have a valid request.
164+
AttachSource(img);
151165
}
152166
}
153-
else if (string.Equals(_uri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
167+
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
154168
{
155-
var source = _uri.OriginalString;
169+
var source = imageUri.OriginalString;
156170
const string base64Head = "base64,";
157171
var index = source.IndexOf(base64Head);
158172
if (index >= 0)
159173
{
160174
var bytes = Convert.FromBase64String(source.Substring(index + base64Head.Length));
161175
var bitmap = new BitmapImage();
162-
AttachSource(bitmap);
163176
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
177+
178+
if (!_tokenSource.IsCancellationRequested)
179+
{
180+
AttachSource(bitmap);
181+
}
164182
}
165183
}
166184
else
167185
{
168-
AttachSource(new BitmapImage(_uri)
186+
AttachSource(new BitmapImage(imageUri)
169187
{
170188
CreateOptions = BitmapCreateOptions.IgnoreImageCache
171189
});
172190
}
173191
}
174192
}
175193

176-
private async Task SetHttpSourceCustomCached(Uri imageUri)
194+
/// <summary>
195+
/// This method is provided in case a developer would like their own custom caching strategy for <see cref="ImageExBase"/>.
196+
/// By default it uses the built-in UWP cache provided by <see cref="BitmapImage"/> and
197+
/// the <see cref="Image"/> control itself. This method should return an <see cref="ImageSource"/>
198+
/// value of the image specified by the provided uri parameter.
199+
/// A <see cref="CancellationToken"/> is provided in case the current request is invalidated
200+
/// (e.g. the container is recycled before the original image is loaded).
201+
/// The Toolkit also has an image cache helper which can be used as well:
202+
/// <see cref="CacheBase{T}.GetFromCacheAsync(Uri, bool, CancellationToken, List{KeyValuePair{string, object}})"/> in <see cref="ImageCache"/>.
203+
/// </summary>
204+
/// <example>
205+
/// <code>
206+
/// var propValues = new List&lt;KeyValuePair&lt;string, object>>();
207+
///
208+
/// if (DecodePixelHeight > 0)
209+
/// {
210+
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelHeight), DecodePixelHeight));
211+
/// }
212+
/// if (DecodePixelWidth > 0)
213+
/// {
214+
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelWidth), DecodePixelWidth));
215+
/// }
216+
/// if (propValues.Count > 0)
217+
/// {
218+
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelType), DecodePixelType));
219+
/// }
220+
///
221+
/// // A token is provided here as well to cancel the request to the cache,
222+
/// // if a new image is requested.
223+
/// return await ImageCache.Instance.GetFromCacheAsync(imageUri, true, token, propValues);
224+
/// </code>
225+
/// </example>
226+
/// <param name="imageUri"><see cref="Uri"/> of the image to load from the cache.</param>
227+
/// <param name="token">A <see cref="CancellationToken"/> which is used to signal when the current request is outdated.</param>
228+
/// <returns><see cref="Task"/></returns>
229+
protected virtual Task<ImageSource> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
177230
{
178-
try
179-
{
180-
var propValues = new List<KeyValuePair<string, object>>();
181-
182-
if (DecodePixelHeight > 0)
183-
{
184-
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelHeight), DecodePixelHeight));
185-
}
186-
187-
if (DecodePixelWidth > 0)
188-
{
189-
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelWidth), DecodePixelWidth));
190-
}
191-
192-
if (propValues.Count > 0)
193-
{
194-
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelType), DecodePixelType));
195-
}
196-
197-
var img = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, _tokenSource.Token, propValues);
198-
199-
lock (LockObj)
200-
{
201-
// If you have many imageEx in a virtualized ListView for instance
202-
// controls will be recycled and the uri will change while waiting for the previous one to load
203-
if (_uri == imageUri)
204-
{
205-
AttachSource(img);
206-
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
207-
VisualStateManager.GoToState(this, LoadedState, true);
208-
}
209-
}
210-
}
211-
catch (OperationCanceledException)
212-
{
213-
// nothing to do as cancellation has been requested.
214-
}
215-
catch (Exception e)
216-
{
217-
lock (LockObj)
218-
{
219-
if (_uri == imageUri)
220-
{
221-
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
222-
VisualStateManager.GoToState(this, FailedState, true);
223-
}
224-
}
225-
}
231+
// By default we just use the built-in UWP image cache provided within the Image control.
232+
return Task.FromResult((ImageSource)new BitmapImage(imageUri));
226233
}
227234
}
228235
}

0 commit comments

Comments
 (0)