From e6af027195212232fd3b62c7a362f0c672448ee0 Mon Sep 17 00:00:00 2001 From: Thomas Levesque Date: Sat, 4 Jan 2020 23:50:29 +0100 Subject: [PATCH 1/3] Cache keyframes instead of whole animation So that animation with different repeat behaviors don't need to be cached separately. --- WpfAnimatedGif/AnimationCache.cs | 58 +++++++------ WpfAnimatedGif/ImageBehavior.cs | 144 ++++++++++++++++--------------- 2 files changed, 108 insertions(+), 94 deletions(-) diff --git a/WpfAnimatedGif/AnimationCache.cs b/WpfAnimatedGif/AnimationCache.cs index 177a92f..4cdf867 100644 --- a/WpfAnimatedGif/AnimationCache.cs +++ b/WpfAnimatedGif/AnimationCache.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; @@ -8,21 +9,18 @@ namespace WpfAnimatedGif { static class AnimationCache { - private class CacheKey + private struct CacheKey { private readonly ImageSource _source; - private readonly RepeatBehavior _repeatBehavior; - public CacheKey(ImageSource source, RepeatBehavior repeatBehavior) + public CacheKey(ImageSource source) { _source = source; - _repeatBehavior = repeatBehavior; } private bool Equals(CacheKey other) { - return ImageEquals(_source, other._source) - && Equals(_repeatBehavior, other._repeatBehavior); + return ImageEquals(_source, other._source); } public override bool Equals(object obj) @@ -35,10 +33,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - unchecked - { - return (ImageGetHashCode(_source) * 397) ^ _repeatBehavior.GetHashCode(); - } + return ImageGetHashCode(_source); } private static int ImageGetHashCode(ImageSource image) @@ -99,21 +94,21 @@ private static Uri GetUri(ImageSource image) } } - private static readonly Dictionary _animationCache = new Dictionary(); + private static readonly Dictionary _animationCache = new Dictionary(); private static readonly Dictionary _referenceCount = new Dictionary(); - public static void IncrementReferenceCount(ImageSource source, RepeatBehavior repeatBehavior) + public static void IncrementReferenceCount(ImageSource source) { - var cacheKey = new CacheKey(source, repeatBehavior); + var cacheKey = new CacheKey(source); int count; _referenceCount.TryGetValue(cacheKey, out count); count++; _referenceCount[cacheKey] = count; } - public static void DecrementReferenceCount(ImageSource source, RepeatBehavior repeatBehavior) + public static void DecrementReferenceCount(ImageSource source) { - var cacheKey = new CacheKey(source, repeatBehavior); + var cacheKey = new CacheKey(source); int count; _referenceCount.TryGetValue(cacheKey, out count); if (count > 0) @@ -128,24 +123,37 @@ public static void DecrementReferenceCount(ImageSource source, RepeatBehavior re } } - public static void AddAnimation(ImageSource source, RepeatBehavior repeatBehavior, ObjectAnimationUsingKeyFrames animation) + public static void Add(ImageSource source, AnimationCacheEntry entry) { - var key = new CacheKey(source, repeatBehavior); - _animationCache[key] = animation; + var key = new CacheKey(source); + _animationCache[key] = entry; } - public static void RemoveAnimation(ImageSource source, RepeatBehavior repeatBehavior, ObjectAnimationUsingKeyFrames animation) + public static void Remove(ImageSource source) { - var key = new CacheKey(source, repeatBehavior); + var key = new CacheKey(source); _animationCache.Remove(key); } - public static ObjectAnimationUsingKeyFrames GetAnimation(ImageSource source, RepeatBehavior repeatBehavior) + public static AnimationCacheEntry Get(ImageSource source) + { + var key = new CacheKey(source); + _animationCache.TryGetValue(key, out var entry); + return entry; + } + } + + internal class AnimationCacheEntry + { + public AnimationCacheEntry(ObjectKeyFrameCollection keyFrames, Duration duration, int repeatCountFromMetadata) { - var key = new CacheKey(source, repeatBehavior); - ObjectAnimationUsingKeyFrames animation; - _animationCache.TryGetValue(key, out animation); - return animation; + KeyFrames = keyFrames; + Duration = duration; + RepeatCountFromMetadata = repeatCountFromMetadata; } + + public ObjectKeyFrameCollection KeyFrames { get; } + public Duration Duration { get; } + public int RepeatCountFromMetadata { get; } } } \ No newline at end of file diff --git a/WpfAnimatedGif/ImageBehavior.cs b/WpfAnimatedGif/ImageBehavior.cs index 27063df..0ed8e96 100644 --- a/WpfAnimatedGif/ImageBehavior.cs +++ b/WpfAnimatedGif/ImageBehavior.cs @@ -292,7 +292,7 @@ private static void AnimatedSourceChanged(DependencyObject o, DependencyProperty imageControl.Unloaded -= ImageControlUnloaded; imageControl.IsVisibleChanged -= VisibilityChanged; - AnimationCache.DecrementReferenceCount(oldValue, GetRepeatBehavior(imageControl)); + AnimationCache.DecrementReferenceCount(oldValue); var controller = GetAnimationController(imageControl); if (controller != null) controller.Dispose(); @@ -342,7 +342,7 @@ static void ImageControlUnloaded(object sender, RoutedEventArgs e) return; var source = GetAnimatedSource(imageControl); if (source != null) - AnimationCache.DecrementReferenceCount(source, GetRepeatBehavior(imageControl)); + AnimationCache.DecrementReferenceCount(source); var controller = GetAnimationController(imageControl); if (controller != null) controller.Dispose(); @@ -357,8 +357,6 @@ private static void RepeatBehaviorChanged(DependencyObject o, DependencyProperty ImageSource source = GetAnimatedSource(imageControl); if (source != null) { - if (!Equals(e.OldValue, e.NewValue)) - AnimationCache.DecrementReferenceCount(source, (RepeatBehavior)e.OldValue); if (imageControl.IsLoaded) InitAnimationOrImage(imageControl); } @@ -445,59 +443,69 @@ private static void InitAnimationOrImage(Image imageControl) private static ObjectAnimationUsingKeyFrames GetAnimation(Image imageControl, BitmapSource source) { - var animation = AnimationCache.GetAnimation(source, GetRepeatBehavior(imageControl)); - if (animation != null) - return animation; - GifFile gifMetadata; - var decoder = GetDecoder(source, imageControl, out gifMetadata) as GifBitmapDecoder; - if (decoder != null && decoder.Frames.Count > 1) - { - var fullSize = GetFullSize(decoder, gifMetadata); - int index = 0; - animation = new ObjectAnimationUsingKeyFrames(); - var totalDuration = TimeSpan.Zero; - BitmapSource baseFrame = null; - foreach (var rawFrame in decoder.Frames) + var cacheEntry = AnimationCache.Get(source); + if (cacheEntry == null) + { + var decoder = GetDecoder(source, imageControl, out GifFile gifMetadata) as GifBitmapDecoder; + if (decoder != null && decoder.Frames.Count > 1) { - var metadata = GetFrameMetadata(decoder, gifMetadata, index); - - var frame = MakeFrame(fullSize, rawFrame, metadata, baseFrame); - var keyFrame = new DiscreteObjectKeyFrame(frame, totalDuration); - animation.KeyFrames.Add(keyFrame); - - totalDuration += metadata.Delay; - - switch (metadata.DisposalMethod) + var fullSize = GetFullSize(decoder, gifMetadata); + int index = 0; + var keyFrames = new ObjectKeyFrameCollection(); + var totalDuration = TimeSpan.Zero; + BitmapSource baseFrame = null; + foreach (var rawFrame in decoder.Frames) { - case FrameDisposalMethod.None: - case FrameDisposalMethod.DoNotDispose: - baseFrame = frame; - break; - case FrameDisposalMethod.RestoreBackground: - if (IsFullFrame(metadata, fullSize)) - { - baseFrame = null; - } - else - { - baseFrame = ClearArea(frame, metadata); - } - break; - case FrameDisposalMethod.RestorePrevious: - // Reuse same base frame - break; + var metadata = GetFrameMetadata(decoder, gifMetadata, index); + + var frame = MakeFrame(fullSize, rawFrame, metadata, baseFrame); + var keyFrame = new DiscreteObjectKeyFrame(frame, totalDuration); + keyFrames.Add(keyFrame); + + totalDuration += metadata.Delay; + + switch (metadata.DisposalMethod) + { + case FrameDisposalMethod.None: + case FrameDisposalMethod.DoNotDispose: + baseFrame = frame; + break; + case FrameDisposalMethod.RestoreBackground: + if (IsFullFrame(metadata, fullSize)) + { + baseFrame = null; + } + else + { + baseFrame = ClearArea(frame, metadata); + } + break; + case FrameDisposalMethod.RestorePrevious: + // Reuse same base frame + break; + } + + index++; } - - index++; + + int repeatCount = GetRepeatCountFromMetadata(decoder, gifMetadata); + cacheEntry = new AnimationCacheEntry(keyFrames, totalDuration, repeatCount); + AnimationCache.Add(source, cacheEntry); } - animation.Duration = totalDuration; - - animation.RepeatBehavior = GetActualRepeatBehavior(imageControl, decoder, gifMetadata); + } - AnimationCache.AddAnimation(source, GetRepeatBehavior(imageControl), animation); - AnimationCache.IncrementReferenceCount(source, GetRepeatBehavior(imageControl)); + if (cacheEntry != null) + { + var animation = new ObjectAnimationUsingKeyFrames + { + KeyFrames = cacheEntry.KeyFrames, + Duration = cacheEntry.Duration, + RepeatBehavior = GetActualRepeatBehavior(imageControl, cacheEntry.RepeatCountFromMetadata) + }; + AnimationCache.IncrementReferenceCount(source); return animation; } + return null; } @@ -712,37 +720,35 @@ private static BitmapSource MakeFrame( return result; } - private static RepeatBehavior GetActualRepeatBehavior(Image imageControl, BitmapDecoder decoder, GifFile gifMetadata) + private static RepeatBehavior GetActualRepeatBehavior(Image imageControl, int repeatCountFromMetadata) { // If specified explicitly, use this value var repeatBehavior = GetRepeatBehavior(imageControl); if (repeatBehavior != default(RepeatBehavior)) return repeatBehavior; - int repeatCount; - if (gifMetadata != null) - { - repeatCount = gifMetadata.RepeatCount; - } - else - { - repeatCount = GetRepeatCount(decoder); - } - if (repeatCount == 0) + if (repeatCountFromMetadata == 0) return RepeatBehavior.Forever; - return new RepeatBehavior(repeatCount); + return new RepeatBehavior(repeatCountFromMetadata); } - private static int GetRepeatCount(BitmapDecoder decoder) + private static int GetRepeatCountFromMetadata(BitmapDecoder decoder, GifFile gifMetadata) { - var ext = GetApplicationExtension(decoder, "NETSCAPE2.0"); - if (ext != null) + if (gifMetadata != null) + { + return gifMetadata.RepeatCount; + } + else { - byte[] bytes = ext.GetQueryOrNull("/Data"); - if (bytes != null && bytes.Length >= 4) - return BitConverter.ToUInt16(bytes, 2); + var ext = GetApplicationExtension(decoder, "NETSCAPE2.0"); + if (ext != null) + { + byte[] bytes = ext.GetQueryOrNull("/Data"); + if (bytes != null && bytes.Length >= 4) + return BitConverter.ToUInt16(bytes, 2); + } + return 1; } - return 1; } private static BitmapMetadata GetApplicationExtension(BitmapDecoder decoder, string application) From 686889983369459377e375d0e4cecfb17dc00237 Mon Sep 17 00:00:00 2001 From: Thomas Levesque Date: Sun, 5 Jan 2020 00:38:47 +0100 Subject: [PATCH 2/3] More reliable animation cache eviction Based on which controls use an animation, rather than reference counting --- WpfAnimatedGif/AnimationCache.cs | 36 +++++++++++++++++--------------- WpfAnimatedGif/ImageBehavior.cs | 6 +++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/WpfAnimatedGif/AnimationCache.cs b/WpfAnimatedGif/AnimationCache.cs index 4cdf867..b6babf4 100644 --- a/WpfAnimatedGif/AnimationCache.cs +++ b/WpfAnimatedGif/AnimationCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Windows; +using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; @@ -95,31 +96,32 @@ private static Uri GetUri(ImageSource image) } private static readonly Dictionary _animationCache = new Dictionary(); - private static readonly Dictionary _referenceCount = new Dictionary(); + private static readonly Dictionary> _imageControls = new Dictionary>(); - public static void IncrementReferenceCount(ImageSource source) + public static void AddControlForSource(ImageSource source, Image imageControl) { var cacheKey = new CacheKey(source); - int count; - _referenceCount.TryGetValue(cacheKey, out count); - count++; - _referenceCount[cacheKey] = count; + if (!_imageControls.TryGetValue(cacheKey, out var controls)) + { + _imageControls[cacheKey] = controls = new HashSet(); + } + + controls.Add(imageControl); } - public static void DecrementReferenceCount(ImageSource source) + public static void RemoveControlForSource(ImageSource source, Image imageControl) { var cacheKey = new CacheKey(source); - int count; - _referenceCount.TryGetValue(cacheKey, out count); - if (count > 0) - { - count--; - _referenceCount[cacheKey] = count; - } - if (count == 0) + if (_imageControls.TryGetValue(cacheKey, out var controls)) { - _animationCache.Remove(cacheKey); - _referenceCount.Remove(cacheKey); + if (controls.Remove(imageControl)) + { + if (controls.Count == 0) + { + _animationCache.Remove(cacheKey); + _imageControls.Remove(cacheKey); + } + } } } diff --git a/WpfAnimatedGif/ImageBehavior.cs b/WpfAnimatedGif/ImageBehavior.cs index 0ed8e96..7bcbdb7 100644 --- a/WpfAnimatedGif/ImageBehavior.cs +++ b/WpfAnimatedGif/ImageBehavior.cs @@ -292,7 +292,7 @@ private static void AnimatedSourceChanged(DependencyObject o, DependencyProperty imageControl.Unloaded -= ImageControlUnloaded; imageControl.IsVisibleChanged -= VisibilityChanged; - AnimationCache.DecrementReferenceCount(oldValue); + AnimationCache.RemoveControlForSource(oldValue, imageControl); var controller = GetAnimationController(imageControl); if (controller != null) controller.Dispose(); @@ -342,7 +342,7 @@ static void ImageControlUnloaded(object sender, RoutedEventArgs e) return; var source = GetAnimatedSource(imageControl); if (source != null) - AnimationCache.DecrementReferenceCount(source); + AnimationCache.RemoveControlForSource(source, imageControl); var controller = GetAnimationController(imageControl); if (controller != null) controller.Dispose(); @@ -502,7 +502,7 @@ private static ObjectAnimationUsingKeyFrames GetAnimation(Image imageControl, Bi Duration = cacheEntry.Duration, RepeatBehavior = GetActualRepeatBehavior(imageControl, cacheEntry.RepeatCountFromMetadata) }; - AnimationCache.IncrementReferenceCount(source); + AnimationCache.AddControlForSource(source, imageControl); return animation; } From 72ec92deaa633be4787b2721cf231b7c18422992 Mon Sep 17 00:00:00 2001 From: Thomas Levesque Date: Sun, 5 Jan 2020 01:38:11 +0100 Subject: [PATCH 3/3] Implement AnimationSpeedRatio & AnimationDuration --- WpfAnimatedGif.Demo/MainWindow.xaml | 42 +++++++++- WpfAnimatedGif.Demo/MainWindow.xaml.cs | 74 ++++++++++++++++++ WpfAnimatedGif/ImageBehavior.cs | 101 +++++++++++++++++++++++-- 3 files changed, 208 insertions(+), 9 deletions(-) diff --git a/WpfAnimatedGif.Demo/MainWindow.xaml b/WpfAnimatedGif.Demo/MainWindow.xaml index 5a24b32..fcbe105 100644 --- a/WpfAnimatedGif.Demo/MainWindow.xaml +++ b/WpfAnimatedGif.Demo/MainWindow.xaml @@ -28,6 +28,8 @@ + + @@ -57,21 +59,51 @@ Click="btnOpenUrl_Click"/> - +