-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Remove Custom Cache in ImageEx to evaluate binary impact size #3736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e24db50
c43f68a
0ad2fc9
dbd379c
628d1d5
9c1dcdf
8239841
9d9f253
c2fc4ad
b386021
d29e7b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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> | ||
|
|
@@ -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) | ||
|
|
@@ -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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: is toggling this visual state unconditionally by design? Because if the input is not I'm thinking maybe we could skip a couple of visual states here and:
It's a minor change but in theory it might help with performance a tiny bit in cases where we have a lot of controls being displayed eg. in a virtualized list, as those would be toggling the target image all the time? |
||
|
|
||
| if (source == null) | ||
| { | ||
| VisualStateManager.GoToState(this, UnloadedState, true); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -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<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)); | ||
| /// } | ||
| /// | ||
| /// // 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)); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we could move the initialization of this instance down below after our synchronous paths are taken, so that in case the image is loaded synchronously instead we can skip the allocation here entirely. Basically we really only need to create this if
ProvideCachedResourceAsyncis called, otherwise we don't use the token anywhere else 🙂