Procházet zdrojové kódy

Refactor, update code for animated gif and improve performance

Ruben před 2 roky
rodič
revize
d3224a0fa1
44 změnil soubory, kde provedl 2406 přidání a 2372 odebrání
  1. 1 7
      src/PicView/ChangeImage/UpdateImage.cs
  2. 431 456
      src/XamlAnimatedGif/AnimationBehavior.cs
  3. 8 9
      src/XamlAnimatedGif/AnimationCompletedEventArgs.cs
  4. 18 19
      src/XamlAnimatedGif/AnimationErrorEventArgs.cs
  5. 8 9
      src/XamlAnimatedGif/AnimationStartedEventArgs.cs
  6. 505 534
      src/XamlAnimatedGif/Animator.cs
  7. 47 40
      src/XamlAnimatedGif/BrushAnimator.cs
  8. 25 30
      src/XamlAnimatedGif/CancellationExtensions.cs
  9. 39 45
      src/XamlAnimatedGif/Decoding/GifApplicationExtension.cs
  10. 17 19
      src/XamlAnimatedGif/Decoding/GifBlock.cs
  11. 8 9
      src/XamlAnimatedGif/Decoding/GifBlockKind.cs
  12. 15 16
      src/XamlAnimatedGif/Decoding/GifColor.cs
  13. 27 32
      src/XamlAnimatedGif/Decoding/GifCommentExtension.cs
  14. 72 72
      src/XamlAnimatedGif/Decoding/GifDataStream.cs
  15. 16 11
      src/XamlAnimatedGif/Decoding/GifDecoderException.cs
  16. 22 21
      src/XamlAnimatedGif/Decoding/GifExtension.cs
  17. 32 37
      src/XamlAnimatedGif/Decoding/GifFrame.cs
  18. 8 9
      src/XamlAnimatedGif/Decoding/GifFrameDisposalMethod.cs
  19. 41 48
      src/XamlAnimatedGif/Decoding/GifGraphicControlExtension.cs
  20. 26 31
      src/XamlAnimatedGif/Decoding/GifHeader.cs
  21. 85 84
      src/XamlAnimatedGif/Decoding/GifHelpers.cs
  22. 20 22
      src/XamlAnimatedGif/Decoding/GifImageData.cs
  23. 34 37
      src/XamlAnimatedGif/Decoding/GifImageDescriptor.cs
  24. 37 46
      src/XamlAnimatedGif/Decoding/GifLogicalScreenDescriptor.cs
  25. 53 60
      src/XamlAnimatedGif/Decoding/GifPlainTextExtension.cs
  26. 11 17
      src/XamlAnimatedGif/Decoding/GifTrailer.cs
  27. 7 8
      src/XamlAnimatedGif/Decoding/IGifRect.cs
  28. 15 10
      src/XamlAnimatedGif/Decoding/InvalidBlockSizeException.cs
  29. 15 10
      src/XamlAnimatedGif/Decoding/InvalidSignatureException.cs
  30. 16 12
      src/XamlAnimatedGif/Decoding/UnknownBlockTypeException.cs
  31. 16 12
      src/XamlAnimatedGif/Decoding/UnknownExtensionTypeException.cs
  32. 16 12
      src/XamlAnimatedGif/Decoding/UnsupportedGifVersionException.cs
  33. 47 45
      src/XamlAnimatedGif/Decompression/BitReader.cs
  34. 194 194
      src/XamlAnimatedGif/Decompression/LzwDecompressStream.cs
  35. 0 16
      src/XamlAnimatedGif/DownloadProgressEventArgs.cs
  36. 0 17
      src/XamlAnimatedGif/Extensions/BitArrayExtensions.cs
  37. 44 44
      src/XamlAnimatedGif/Extensions/StreamExtensions.cs
  38. 18 20
      src/XamlAnimatedGif/Extensions/WritableBitmapExtensions.cs
  39. 52 44
      src/XamlAnimatedGif/ImageAnimator.cs
  40. 4 5
      src/XamlAnimatedGif/Properties/Xmlns.cs
  41. 92 91
      src/XamlAnimatedGif/TimingManager.cs
  42. 59 108
      src/XamlAnimatedGif/UriLoader.cs
  43. 201 0
      src/XamlAnimatedGif/XamlAnimatedGif LICENSE.txt
  44. 4 4
      src/XamlAnimatedGif/XamlAnimatedGif.csproj

+ 1 - 7
src/PicView/ChangeImage/UpdateImage.cs

@@ -2,12 +2,9 @@
 using PicView.ImageHandling;
 using PicView.PicGallery;
 using PicView.Properties;
-using PicView.SystemIntegration;
 using PicView.UILogic;
 using PicView.UILogic.Sizing;
 using PicView.UILogic.TransformImage;
-using System.IO;
-using System.Reflection;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Media.Imaging;
@@ -82,10 +79,7 @@ internal static class UpdateImage
         if (preLoadValue.FileInfo.Extension.Equals(".gif", StringComparison.OrdinalIgnoreCase))
         {
             var uri = new Uri(Pics?[index]);
-            await ConfigureWindows.GetMainWindow.MainImage.Dispatcher.InvokeAsync(() =>
-            {
-                AnimationBehavior.SetSourceUri(ConfigureWindows.GetMainWindow.MainImage, uri);
-            }, DispatcherPriority.Normal);
+            AnimationBehavior.SetSourceUri(ConfigureWindows.GetMainWindow.MainImage, uri);
         }
 
         if (GetToolTipMessage is { IsVisible: true })

+ 431 - 456
src/XamlAnimatedGif/AnimationBehavior.cs

@@ -1,602 +1,577 @@
-using XamlAnimatedGif.Decoding;
-using System;
+using System.ComponentModel;
 using System.IO;
-using System.Threading.Tasks;
-using XamlAnimatedGif.Extensions;
-using System.ComponentModel;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Markup;
 using System.Windows.Media.Animation;
 using System.Windows.Media.Imaging;
-using System.Threading;
+using XamlAnimatedGif.Decoding;
+using XamlAnimatedGif.Extensions;
+
+namespace XamlAnimatedGif;
 
-namespace XamlAnimatedGif
+public static class AnimationBehavior
 {
-    public static class AnimationBehavior
-    {
-        #region Public attached properties and events
+    #region Public attached properties and events
 
-        #region SourceUri
+    #region SourceUri
 
-        [AttachedPropertyBrowsableForType(typeof(Image))]
-        public static Uri GetSourceUri(Image image)
-        {
-            return (Uri)image.GetValue(SourceUriProperty);
-        }
+    [AttachedPropertyBrowsableForType(typeof(Image))]
+    public static Uri GetSourceUri(Image image)
+    {
+        return (Uri)image.GetValue(SourceUriProperty);
+    }
 
-        public static void SetSourceUri(Image image, Uri value)
-        {
-            image.SetValue(SourceUriProperty, value);
-        }
+    public static void SetSourceUri(Image image, Uri value)
+    {
+        image.SetValue(SourceUriProperty, value);
+    }
 
-        public static readonly DependencyProperty SourceUriProperty =
-            DependencyProperty.RegisterAttached(
-              "SourceUri",
-              typeof(Uri),
-              typeof(AnimationBehavior),
-              new PropertyMetadata(
+    public static readonly DependencyProperty SourceUriProperty =
+        DependencyProperty.RegisterAttached(
+            "SourceUri",
+            typeof(Uri),
+            typeof(AnimationBehavior),
+            new PropertyMetadata(
                 null,
                 SourceChanged));
 
-        #endregion SourceUri
+    #endregion SourceUri
 
-        #region SourceStream
+    #region SourceStream
 
-        [AttachedPropertyBrowsableForType(typeof(Image))]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        public static Stream GetSourceStream(DependencyObject obj)
-        {
-            return (Stream)obj.GetValue(SourceStreamProperty);
-        }
+    [AttachedPropertyBrowsableForType(typeof(Image))]
+    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+    public static Stream GetSourceStream(DependencyObject obj)
+    {
+        return (Stream)obj.GetValue(SourceStreamProperty);
+    }
 
-        public static void SetSourceStream(DependencyObject obj, Stream value)
-        {
-            obj.SetValue(SourceStreamProperty, value);
-        }
+    public static void SetSourceStream(DependencyObject obj, Stream value)
+    {
+        obj.SetValue(SourceStreamProperty, value);
+    }
 
-        public static readonly DependencyProperty SourceStreamProperty =
-            DependencyProperty.RegisterAttached(
-                "SourceStream",
-                typeof(Stream),
-                typeof(AnimationBehavior),
-                new PropertyMetadata(
-                    null,
-                    SourceChanged));
+    public static readonly DependencyProperty SourceStreamProperty =
+        DependencyProperty.RegisterAttached(
+            "SourceStream",
+            typeof(Stream),
+            typeof(AnimationBehavior),
+            new PropertyMetadata(
+                null,
+                SourceChanged));
 
-        #endregion SourceStream
+    #endregion SourceStream
 
-        #region RepeatBehavior
+    #region RepeatBehavior
 
-        [AttachedPropertyBrowsableForType(typeof(Image))]
-        public static RepeatBehavior GetRepeatBehavior(DependencyObject obj)
-        {
-            return (RepeatBehavior)obj.GetValue(RepeatBehaviorProperty);
-        }
+    [AttachedPropertyBrowsableForType(typeof(Image))]
+    public static RepeatBehavior GetRepeatBehavior(DependencyObject obj)
+    {
+        return (RepeatBehavior)obj.GetValue(RepeatBehaviorProperty);
+    }
 
-        public static void SetRepeatBehavior(DependencyObject obj, RepeatBehavior value)
-        {
-            obj.SetValue(RepeatBehaviorProperty, value);
-        }
+    public static void SetRepeatBehavior(DependencyObject obj, RepeatBehavior value)
+    {
+        obj.SetValue(RepeatBehaviorProperty, value);
+    }
 
-        public static readonly DependencyProperty RepeatBehaviorProperty =
-            DependencyProperty.RegisterAttached(
-              "RepeatBehavior",
-              typeof(RepeatBehavior),
-              typeof(AnimationBehavior),
-              new PropertyMetadata(
+    public static readonly DependencyProperty RepeatBehaviorProperty =
+        DependencyProperty.RegisterAttached(
+            "RepeatBehavior",
+            typeof(RepeatBehavior),
+            typeof(AnimationBehavior),
+            new PropertyMetadata(
                 default(RepeatBehavior),
                 RepeatBehaviorChanged));
 
-        #endregion RepeatBehavior
+    #endregion RepeatBehavior
 
-        #region CacheFramesInMemory
+    #region CacheFramesInMemory
 
-        public static void SetCacheFramesInMemory(DependencyObject element, bool value)
-        {
-            element.SetValue(CacheFramesInMemoryProperty, value);
-        }
+    public static void SetCacheFramesInMemory(DependencyObject element, bool value)
+    {
+        element.SetValue(CacheFramesInMemoryProperty, value);
+    }
 
-        [AttachedPropertyBrowsableForType(typeof(Image))]
-        public static bool GetCacheFramesInMemory(DependencyObject element)
-        {
-            return (bool)element.GetValue(CacheFramesInMemoryProperty);
-        }
+    [AttachedPropertyBrowsableForType(typeof(Image))]
+    public static bool GetCacheFramesInMemory(DependencyObject element)
+    {
+        return (bool)element.GetValue(CacheFramesInMemoryProperty);
+    }
 
-        public static readonly DependencyProperty CacheFramesInMemoryProperty =
-            DependencyProperty.RegisterAttached(
+    public static readonly DependencyProperty CacheFramesInMemoryProperty =
+        DependencyProperty.RegisterAttached(
             "CacheFramesInMemory",
             typeof(bool),
             typeof(AnimationBehavior),
             new PropertyMetadata(false, SourceChanged));
 
-        #endregion CacheFramesInMemory
-
-        #region AutoStart
-
-        [AttachedPropertyBrowsableForType(typeof(Image))]
-        public static bool GetAutoStart(DependencyObject obj)
-        {
-            return (bool)obj.GetValue(AutoStartProperty);
-        }
-
-        public static void SetAutoStart(DependencyObject obj, bool value)
-        {
-            obj.SetValue(AutoStartProperty, value);
-        }
-
-        public static readonly DependencyProperty AutoStartProperty =
-            DependencyProperty.RegisterAttached(
-                "AutoStart",
-                typeof(bool),
-                typeof(AnimationBehavior),
-                new PropertyMetadata(true));
-
-        #endregion AutoStart
-
-        #region AnimateInDesignMode
+    #endregion CacheFramesInMemory
 
-        public static bool GetAnimateInDesignMode(DependencyObject obj)
-        {
-            return (bool)obj.GetValue(AnimateInDesignModeProperty);
-        }
-
-        public static void SetAnimateInDesignMode(DependencyObject obj, bool value)
-        {
-            obj.SetValue(AnimateInDesignModeProperty, value);
-        }
+    #region AutoStart
 
-        public static readonly DependencyProperty AnimateInDesignModeProperty =
-            DependencyProperty.RegisterAttached(
-                "AnimateInDesignMode",
-                typeof(bool),
-                typeof(AnimationBehavior),
-                new PropertyMetadata(
-                    false,
-                    AnimateInDesignModeChanged));
+    [AttachedPropertyBrowsableForType(typeof(Image))]
+    public static bool GetAutoStart(DependencyObject obj)
+    {
+        return (bool)obj.GetValue(AutoStartProperty);
+    }
 
-        #endregion AnimateInDesignMode
+    public static void SetAutoStart(DependencyObject obj, bool value)
+    {
+        obj.SetValue(AutoStartProperty, value);
+    }
 
-        #region Animator
+    public static readonly DependencyProperty AutoStartProperty =
+        DependencyProperty.RegisterAttached(
+            "AutoStart",
+            typeof(bool),
+            typeof(AnimationBehavior),
+            new PropertyMetadata(true));
 
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        public static Animator GetAnimator(DependencyObject obj)
-        {
-            return (Animator)obj.GetValue(AnimatorProperty);
-        }
+    #endregion AutoStart
 
-        private static void SetAnimator(DependencyObject obj, Animator value)
-        {
-            obj.SetValue(AnimatorProperty, value);
-        }
+    #region AnimateInDesignMode
 
-        public static readonly DependencyProperty AnimatorProperty =
-            DependencyProperty.RegisterAttached(
-                "Animator",
-                typeof(Animator),
-                typeof(AnimationBehavior),
-                new PropertyMetadata(null));
+    public static bool GetAnimateInDesignMode(DependencyObject obj)
+    {
+        return (bool)obj.GetValue(AnimateInDesignModeProperty);
+    }
 
-        #endregion Animator
+    public static void SetAnimateInDesignMode(DependencyObject obj, bool value)
+    {
+        obj.SetValue(AnimateInDesignModeProperty, value);
+    }
 
-        #region Error
+    public static readonly DependencyProperty AnimateInDesignModeProperty =
+        DependencyProperty.RegisterAttached(
+            "AnimateInDesignMode",
+            typeof(bool),
+            typeof(AnimationBehavior),
+            new PropertyMetadata(
+                false,
+                AnimateInDesignModeChanged));
 
-        public static readonly RoutedEvent ErrorEvent =
-            EventManager.RegisterRoutedEvent(
-                "Error",
-                RoutingStrategy.Bubble,
-                typeof(AnimationErrorEventHandler),
-                typeof(AnimationBehavior));
+    #endregion AnimateInDesignMode
 
-        public static void AddErrorHandler(DependencyObject d, AnimationErrorEventHandler handler)
-        {
-            (d as UIElement)?.AddHandler(ErrorEvent, handler);
-        }
+    #region Animator
 
-        public static void RemoveErrorHandler(DependencyObject d, AnimationErrorEventHandler handler)
-        {
-            (d as UIElement)?.RemoveHandler(ErrorEvent, handler);
-        }
+    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+    public static Animator GetAnimator(DependencyObject obj)
+    {
+        return (Animator)obj.GetValue(AnimatorProperty);
+    }
 
-        internal static void OnError(Image image, Exception exception, AnimationErrorKind kind)
-        {
-            image.RaiseEvent(new AnimationErrorEventArgs(image, exception, kind));
-        }
+    private static void SetAnimator(DependencyObject obj, Animator value)
+    {
+        obj.SetValue(AnimatorProperty, value);
+    }
 
-        private static void AnimatorError(object sender, AnimationErrorEventArgs e)
-        {
-            var source = e.Source as UIElement;
-            source?.RaiseEvent(e);
-        }
+    public static readonly DependencyProperty AnimatorProperty =
+        DependencyProperty.RegisterAttached(
+            "Animator",
+            typeof(Animator),
+            typeof(AnimationBehavior),
+            new PropertyMetadata(null));
 
-        #endregion Error
+    #endregion Animator
 
-        #region DownloadProgress
+    #region Error
 
-        public static readonly RoutedEvent DownloadProgressEvent =
-            EventManager.RegisterRoutedEvent(
-                "DownloadProgress",
-                RoutingStrategy.Bubble,
-                typeof(DownloadProgressEventHandler),
-                typeof(AnimationBehavior));
+    public static readonly RoutedEvent ErrorEvent =
+        EventManager.RegisterRoutedEvent(
+            "Error",
+            RoutingStrategy.Bubble,
+            typeof(AnimationErrorEventHandler),
+            typeof(AnimationBehavior));
 
-        public static void AddDownloadProgressHandler(DependencyObject d, DownloadProgressEventHandler handler)
-        {
-            (d as UIElement)?.AddHandler(DownloadProgressEvent, handler);
-        }
+    public static void AddErrorHandler(DependencyObject d, AnimationErrorEventHandler handler)
+    {
+        (d as UIElement)?.AddHandler(ErrorEvent, handler);
+    }
 
-        public static void RemoveDownloadProgressHandler(DependencyObject d, DownloadProgressEventHandler handler)
-        {
-            (d as UIElement)?.RemoveHandler(DownloadProgressEvent, handler);
-        }
+    public static void RemoveErrorHandler(DependencyObject d, AnimationErrorEventHandler handler)
+    {
+        (d as UIElement)?.RemoveHandler(ErrorEvent, handler);
+    }
 
-        internal static void OnDownloadProgress(Image image, int downloadPercentage)
-        {
-            image.RaiseEvent(new DownloadProgressEventArgs(image, downloadPercentage));
-        }
+    internal static void OnError(Image image, Exception exception, AnimationErrorKind kind)
+    {
+        image.RaiseEvent(new AnimationErrorEventArgs(image, exception, kind));
+    }
 
-        #endregion DownloadProgress
+    private static void AnimatorError(object sender, AnimationErrorEventArgs e)
+    {
+        var source = e.Source as UIElement;
+        source?.RaiseEvent(e);
+    }
 
-        #region Loaded
+    #endregion Error
 
-        public static readonly RoutedEvent LoadedEvent =
-            EventManager.RegisterRoutedEvent(
-                "Loaded",
-                RoutingStrategy.Bubble,
-                typeof(RoutedEventHandler),
-                typeof(AnimationBehavior));
+    #region Loaded
 
-        public static void AddLoadedHandler(DependencyObject d, RoutedEventHandler handler)
-        {
-            (d as UIElement)?.AddHandler(LoadedEvent, handler);
-        }
+    public static readonly RoutedEvent LoadedEvent =
+        EventManager.RegisterRoutedEvent(
+            "Loaded",
+            RoutingStrategy.Bubble,
+            typeof(RoutedEventHandler),
+            typeof(AnimationBehavior));
 
-        public static void RemoveLoadedHandler(DependencyObject d, RoutedEventHandler handler)
-        {
-            (d as UIElement)?.RemoveHandler(LoadedEvent, handler);
-        }
+    public static void AddLoadedHandler(DependencyObject d, RoutedEventHandler handler)
+    {
+        (d as UIElement)?.AddHandler(LoadedEvent, handler);
+    }
 
-        private static void OnLoaded(Image sender)
-        {
-            sender.RaiseEvent(new RoutedEventArgs(LoadedEvent, sender));
-        }
+    public static void RemoveLoadedHandler(DependencyObject d, RoutedEventHandler handler)
+    {
+        (d as UIElement)?.RemoveHandler(LoadedEvent, handler);
+    }
 
-        #endregion Loaded
+    private static void OnLoaded(Image sender)
+    {
+        sender.RaiseEvent(new RoutedEventArgs(LoadedEvent, sender));
+    }
 
-        #region AnimationStarted
+    #endregion Loaded
 
-        public static readonly RoutedEvent AnimationStartedEvent =
-            EventManager.RegisterRoutedEvent(
-                "AnimationStarted",
-                RoutingStrategy.Bubble,
-                typeof(AnimationStartedEventHandler),
-                typeof(AnimationBehavior));
+    #region AnimationStarted
 
-        public static void AddAnimationStartedHandler(DependencyObject d, AnimationStartedEventHandler handler)
-        {
-            (d as UIElement)?.AddHandler(AnimationStartedEvent, handler);
-        }
+    public static readonly RoutedEvent AnimationStartedEvent =
+        EventManager.RegisterRoutedEvent(
+            "AnimationStarted",
+            RoutingStrategy.Bubble,
+            typeof(AnimationStartedEventHandler),
+            typeof(AnimationBehavior));
 
-        public static void RemoveAnimationStartedHandler(DependencyObject d, AnimationStartedEventHandler handler)
-        {
-            (d as UIElement)?.RemoveHandler(AnimationStartedEvent, handler);
-        }
+    public static void AddAnimationStartedHandler(DependencyObject d, AnimationStartedEventHandler handler)
+    {
+        (d as UIElement)?.AddHandler(AnimationStartedEvent, handler);
+    }
 
-        private static void AnimatorAnimationStarted(object sender, AnimationStartedEventArgs e)
-        {
-            (e.Source as Image)?.RaiseEvent(e);
-        }
+    public static void RemoveAnimationStartedHandler(DependencyObject d, AnimationStartedEventHandler handler)
+    {
+        (d as UIElement)?.RemoveHandler(AnimationStartedEvent, handler);
+    }
 
-        #endregion AnimationStarted
+    private static void AnimatorAnimationStarted(object sender, AnimationStartedEventArgs e)
+    {
+        (e.Source as Image)?.RaiseEvent(e);
+    }
 
-        #region AnimationCompleted
+    #endregion AnimationStarted
 
-        public static readonly RoutedEvent AnimationCompletedEvent =
-            EventManager.RegisterRoutedEvent(
-                "AnimationCompleted",
-                RoutingStrategy.Bubble,
-                typeof(AnimationCompletedEventHandler),
-                typeof(AnimationBehavior));
+    #region AnimationCompleted
 
-        public static void AddAnimationCompletedHandler(DependencyObject d, AnimationCompletedEventHandler handler)
-        {
-            (d as UIElement)?.AddHandler(AnimationCompletedEvent, handler);
-        }
+    public static readonly RoutedEvent AnimationCompletedEvent =
+        EventManager.RegisterRoutedEvent(
+            "AnimationCompleted",
+            RoutingStrategy.Bubble,
+            typeof(AnimationCompletedEventHandler),
+            typeof(AnimationBehavior));
 
-        public static void RemoveAnimationCompletedHandler(DependencyObject d, AnimationCompletedEventHandler handler)
-        {
-            (d as UIElement)?.RemoveHandler(AnimationCompletedEvent, handler);
-        }
+    public static void AddAnimationCompletedHandler(DependencyObject d, AnimationCompletedEventHandler handler)
+    {
+        (d as UIElement)?.AddHandler(AnimationCompletedEvent, handler);
+    }
 
-        private static void AnimatorAnimationCompleted(object sender, AnimationCompletedEventArgs e)
-        {
-            (e.Source as Image)?.RaiseEvent(e);
-        }
+    public static void RemoveAnimationCompletedHandler(DependencyObject d, AnimationCompletedEventHandler handler)
+    {
+        (d as UIElement)?.RemoveHandler(AnimationCompletedEvent, handler);
+    }
 
-        #endregion AnimationCompleted
+    private static void AnimatorAnimationCompleted(object sender, AnimationCompletedEventArgs e)
+    {
+        (e.Source as Image)?.RaiseEvent(e);
+    }
 
-        #endregion Public attached properties and events
+    #endregion AnimationCompleted
 
-        #region Private attached properties
+    #endregion Public attached properties and events
 
-        private static int GetSeqNum(DependencyObject obj)
-        {
-            return (int)obj.GetValue(SeqNumProperty);
-        }
+    #region Private attached properties
 
-        private static void SetSeqNum(DependencyObject obj, int value)
-        {
-            obj.SetValue(SeqNumProperty, value);
-        }
+    private static int GetSeqNum(DependencyObject obj)
+    {
+        return (int)obj.GetValue(SeqNumProperty);
+    }
 
-        private static readonly DependencyProperty SeqNumProperty =
-            DependencyProperty.RegisterAttached("SeqNum", typeof(int), typeof(AnimationBehavior), new PropertyMetadata(0));
+    private static void SetSeqNum(DependencyObject obj, int value)
+    {
+        obj.SetValue(SeqNumProperty, value);
+    }
 
-        #endregion Private attached properties
+    private static readonly DependencyProperty SeqNumProperty =
+        DependencyProperty.RegisterAttached("SeqNum", typeof(int), typeof(AnimationBehavior),
+            new PropertyMetadata(0));
 
-        private static void SourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
-        {
-            if (o is not Image image)
-                return;
+    #endregion Private attached properties
 
-            InitAnimation(image);
-        }
+    private static void SourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+    {
+        if (o is not Image image)
+            return;
 
-        private static void RepeatBehaviorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
-        {
-            GetAnimator(o)?.OnRepeatBehaviorChanged();
-        }
+        _ = InitAnimationAsync(image).ConfigureAwait(false);
+    }
 
-        private static void AnimateInDesignModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            if (d is not Image image)
-                return;
+    private static void RepeatBehaviorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+    {
+        GetAnimator(o)?.OnRepeatBehaviorChanged();
+    }
 
-            InitAnimation(image);
-        }
+    private static async void AnimateInDesignModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is not Image image)
+            return;
 
-        private static bool CheckDesignMode(Image image, Uri sourceUri, Stream sourceStream)
-        {
-            if (IsInDesignMode(image) && !GetAnimateInDesignMode(image))
-            {
-                try
-                {
-                    if (sourceStream != null)
-                    {
-                        SetStaticImage(image, sourceStream);
-                    }
-                    else if (sourceUri != null)
-                    {
-                        var bmp = new BitmapImage
-                        {
-                            UriSource = sourceUri
-                        };
-                        image.Source = bmp;
-                    }
-                }
-                catch
-                {
-                    image.Source = null;
-                }
-                return false;
-            }
-            return true;
-        }
+        await InitAnimationAsync(image).ConfigureAwait(false);
+    }
 
-        private static void InitAnimation(Image image)
+    private static bool CheckDesignMode(Image image, Uri sourceUri, Stream sourceStream)
+    {
+        if (IsInDesignMode(image) && !GetAnimateInDesignMode(image))
         {
-            if (IsLoaded(image))
-            {
-                image.Unloaded += Image_Unloaded;
-            }
-            else
-            {
-                image.Loaded += Image_Loaded;
-                return;
-            }
-
-            int seqNum = GetSeqNum(image) + 1;
-            SetSeqNum(image, seqNum);
-            ClearAnimatorCore(image);
-
-            CancellationTokenSource ctx = new CancellationTokenSource();
-            image.Unloaded += (sender, args) => ctx.Cancel();
             try
             {
-                var stream = GetSourceStream(image);
-                if (stream != null)
+                if (sourceStream != null)
                 {
-                    InitAnimationAsync(image, stream.AsBuffered(), GetRepeatBehavior(image), seqNum, GetCacheFramesInMemory(image), ctx.Token);
-                    return;
+                    SetStaticImage(image, sourceStream);
                 }
-
-                var uri = GetAbsoluteUri(image);
-                if (uri != null)
+                else if (sourceUri != null)
                 {
-                    InitAnimationAsync(image, uri, GetRepeatBehavior(image), seqNum, GetCacheFramesInMemory(image), ctx.Token);
+                    var bmp = new BitmapImage
+                    {
+                        UriSource = sourceUri
+                    };
+                    image.Source = bmp;
                 }
             }
-            catch (Exception ex)
+            catch
             {
-                OnError(image, ex, AnimationErrorKind.Loading);
+                image.Source = null;
             }
+
+            return false;
         }
 
-        private static void Image_Loaded(object sender, RoutedEventArgs e)
+        return true;
+    }
+
+    private static async Task InitAnimationAsync(Image image)
+    {
+        if (IsLoaded(image))
         {
-            var image = (Image)sender;
-            image.Loaded -= Image_Loaded;
-            InitAnimation(image);
+            image.Unloaded += Image_Unloaded;
         }
-
-        private static void Image_Unloaded(object sender, RoutedEventArgs e)
+        else
         {
-            var image = (Image)sender;
-            image.Unloaded -= Image_Unloaded;
             image.Loaded += Image_Loaded;
-
-            int seqNum = GetSeqNum(image) + 1;
-            SetSeqNum(image, seqNum);
-            ClearAnimatorCore(image);
+            return;
         }
 
-        private static bool IsLoaded(FrameworkElement element)
-        {
-            return element.IsLoaded;
-        }
+        var seqNum = GetSeqNum(image) + 1;
+        SetSeqNum(image, seqNum);
+        ClearAnimatorCore(image);
 
-        private static Uri GetAbsoluteUri(Image image)
+        try
         {
-            var uri = GetSourceUri(image);
-            if (uri == null)
-                return null;
-            if (!uri.IsAbsoluteUri)
+            var stream = GetSourceStream(image);
+            if (stream != null)
             {
-                var baseUri = ((IUriContext)image).BaseUri;
-                if (baseUri != null)
-                {
-                    uri = new Uri(baseUri, uri);
-                }
-                else
-                {
-                    throw new InvalidOperationException("Relative URI can't be resolved");
-                }
+                InitAnimationAsync(image, stream.AsBuffered(), GetRepeatBehavior(image), seqNum,
+                    GetCacheFramesInMemory(image));
+                return;
             }
-            return uri;
-        }
 
-        private static async void InitAnimationAsync(Image image, Uri sourceUri, RepeatBehavior repeatBehavior, int seqNum, bool cacheFrameDataInMemory, CancellationToken cancellationToken)
+            var uri = GetAbsoluteUri(image);
+            if (uri != null)
+            {
+                await InitAnimationAsync(image, uri, GetRepeatBehavior(image), seqNum, GetCacheFramesInMemory(image))
+                    .ConfigureAwait(false);
+            }
+        }
+        catch (Exception ex)
         {
-            if (!CheckDesignMode(image, sourceUri, null))
-                return;
+            OnError(image, ex, AnimationErrorKind.Loading);
+        }
+    }
 
-            try
-            {
-                var progress = new Progress<int>(percentage => OnDownloadProgress(image, percentage));
-                var animator = await ImageAnimator.CreateAsync(sourceUri, repeatBehavior, progress, image, cacheFrameDataInMemory, cancellationToken);
-                // Check that the source hasn't changed while we were loading the animation
-                if (GetSeqNum(image) != seqNum)
-                {
-                    animator.Dispose();
-                    return;
-                }
+    private static void Image_Loaded(object sender, RoutedEventArgs e)
+    {
+        var image = (Image)sender;
+        image.Loaded -= Image_Loaded;
+        _ = InitAnimationAsync(image).ConfigureAwait(false);
+    }
 
-                SetAnimatorCore(image, animator);
-                OnLoaded(image);
-                await StartAsync(image, animator);
-            }
-            catch (InvalidSignatureException)
+    private static void Image_Unloaded(object sender, RoutedEventArgs e)
+    {
+        var image = (Image)sender;
+        image.Unloaded -= Image_Unloaded;
+        image.Loaded += Image_Loaded;
+
+        var seqNum = GetSeqNum(image) + 1;
+        SetSeqNum(image, seqNum);
+        ClearAnimatorCore(image);
+    }
+
+    private static bool IsLoaded(FrameworkElement element)
+    {
+        return element.IsLoaded;
+    }
+
+    private static Uri GetAbsoluteUri(Image image)
+    {
+        var uri = GetSourceUri(image);
+        if (uri == null)
+            return null;
+        if (!uri.IsAbsoluteUri)
+        {
+            var baseUri = ((IUriContext)image).BaseUri;
+            if (baseUri != null)
             {
-                await SetStaticImageAsync(image, sourceUri);
-                OnLoaded(image);
+                uri = new Uri(baseUri, uri);
             }
-            catch (Exception ex)
+            else
             {
-                OnError(image, ex, AnimationErrorKind.Loading);
+                throw new InvalidOperationException("Relative URI can't be resolved");
             }
         }
 
-        private static async void InitAnimationAsync(Image image, Stream stream, RepeatBehavior repeatBehavior, int seqNum, bool cacheFrameDataInMemory, CancellationToken cancellationToken)
-        {
-            if (!CheckDesignMode(image, null, stream))
-                return;
+        return uri;
+    }
 
-            try
-            {
-                var animator = await ImageAnimator.CreateAsync(stream, repeatBehavior, image, cacheFrameDataInMemory, cancellationToken);
-                // Check that the source hasn't changed while we were loading the animation
-                if (GetSeqNum(image) != seqNum)
-                {
-                    animator.Dispose();
-                    return;
-                }
+    private static async Task InitAnimationAsync(Image image, Uri sourceUri, RepeatBehavior repeatBehavior,
+        int seqNum, bool cacheFrameDataInMemory)
+    {
+        if (!CheckDesignMode(image, sourceUri, null))
+            return;
 
-                SetAnimatorCore(image, animator);
-                OnLoaded(image);
-                await StartAsync(image, animator);
-            }
-            catch (InvalidSignatureException)
-            {
-                SetStaticImage(image, stream);
-                OnLoaded(image);
-            }
-            catch (Exception ex)
+        try
+        {
+            var animator = await ImageAnimator.CreateAsync(sourceUri, repeatBehavior, null, image,
+                cacheFrameDataInMemory);
+            // Check that the source hasn't changed while we were loading the animation
+            if (GetSeqNum(image) != seqNum)
             {
-                OnError(image, ex, AnimationErrorKind.Loading);
+                animator.Dispose();
+                return;
             }
-        }
 
-        private static void SetAnimatorCore(Image image, Animator animator)
+            SetAnimatorCore(image, animator);
+            OnLoaded(image);
+            await StartAsync(image, animator);
+        }
+        catch (InvalidSignatureException)
         {
-            SetAnimator(image, animator);
-            animator.Error += AnimatorError;
-            animator.AnimationStarted += AnimatorAnimationStarted;
-            animator.AnimationCompleted += AnimatorAnimationCompleted;
-            image.Source = animator.Bitmap;
+            await SetStaticImageAsync(image, sourceUri);
+            OnLoaded(image);
         }
-
-        private static async Task StartAsync(Image image, Animator animator)
+        catch (Exception ex)
         {
-            if (GetAutoStart(image))
-                animator.Play();
-            else
-                await animator.ShowFirstFrameAsync();
+            OnError(image, ex, AnimationErrorKind.Loading);
         }
+    }
+
+    private static async void InitAnimationAsync(Image image, Stream stream, RepeatBehavior repeatBehavior,
+        int seqNum, bool cacheFrameDataInMemory)
+    {
+        if (!CheckDesignMode(image, null, stream))
+            return;
 
-        private static void ClearAnimatorCore(Image image)
+        try
         {
-            var animator = GetAnimator(image);
-            if (animator == null)
+            var animator = await ImageAnimator.CreateAsync(stream, repeatBehavior, image, cacheFrameDataInMemory);
+            // Check that the source hasn't changed while we were loading the animation
+            if (GetSeqNum(image) != seqNum)
+            {
+                animator.Dispose();
                 return;
+            }
 
-            animator.AnimationCompleted -= AnimatorAnimationCompleted;
-            animator.AnimationStarted -= AnimatorAnimationStarted;
-            animator.Error -= AnimatorError;
-            animator.Dispose();
-            SetAnimator(image, null);
+            SetAnimatorCore(image, animator);
+            OnLoaded(image);
+            await StartAsync(image, animator);
         }
-
-        // ReSharper disable once UnusedParameter.Local (used in WPF)
-        private static bool IsInDesignMode(DependencyObject obj)
+        catch (InvalidSignatureException)
         {
-            return DesignerProperties.GetIsInDesignMode(obj);
+            SetStaticImage(image, stream);
+            OnLoaded(image);
         }
-
-        private static async Task SetStaticImageAsync(Image image, Uri sourceUri)
+        catch (Exception ex)
         {
-            try
-            {
-                var progress = new Progress<int>(percentage => OnDownloadProgress(image, percentage));
-                using var stream = await UriLoader.GetStreamFromUriAsync(sourceUri, progress);
-                SetStaticImageCore(image, stream);
-            }
-            catch (Exception ex)
-            {
-                OnError(image, ex, AnimationErrorKind.Loading);
-            }
+            OnError(image, ex, AnimationErrorKind.Loading);
         }
+    }
+
+    private static void SetAnimatorCore(Image image, Animator animator)
+    {
+        SetAnimator(image, animator);
+        animator.Error += AnimatorError;
+        animator.AnimationStarted += AnimatorAnimationStarted;
+        animator.AnimationCompleted += AnimatorAnimationCompleted;
+        image.Source = animator.Bitmap;
+    }
+
+    private static async Task StartAsync(Image image, Animator animator)
+    {
+        if (GetAutoStart(image))
+            animator.Play();
+        else
+            await animator.ShowFirstFrameAsync().ConfigureAwait(false);
+    }
+
+    private static void ClearAnimatorCore(Image image)
+    {
+        var animator = GetAnimator(image);
+        if (animator == null)
+            return;
+
+        animator.AnimationCompleted -= AnimatorAnimationCompleted;
+        animator.AnimationStarted -= AnimatorAnimationStarted;
+        animator.Error -= AnimatorError;
+        animator.Dispose();
+        SetAnimator(image, null);
+    }
+
+    // ReSharper disable once UnusedParameter.Local (used in WPF)
+    private static bool IsInDesignMode(DependencyObject obj)
+    {
+        return DesignerProperties.GetIsInDesignMode(obj);
+    }
 
-        private static void SetStaticImage(Image image, Stream stream)
+    private static async Task SetStaticImageAsync(Image image, Uri sourceUri)
+    {
+        try
         {
-            try
-            {
-                SetStaticImageCore(image, stream);
-            }
-            catch (Exception ex)
-            {
-                OnError(image, ex, AnimationErrorKind.Loading);
-            }
+            await using var stream = await UriLoader.GetStreamFromUriAsync(sourceUri);
+            SetStaticImageCore(image, stream);
         }
+        catch (Exception ex)
+        {
+            OnError(image, ex, AnimationErrorKind.Loading);
+        }
+    }
 
-        private static void SetStaticImageCore(Image image, Stream stream)
+    private static void SetStaticImage(Image image, Stream stream)
+    {
+        try
+        {
+            SetStaticImageCore(image, stream);
+        }
+        catch (Exception ex)
         {
-            stream.Seek(0, SeekOrigin.Begin);
-            var bmp = new BitmapImage();
-            bmp.BeginInit();
-            bmp.CacheOption = BitmapCacheOption.OnLoad;
-            bmp.StreamSource = stream;
-            bmp.EndInit();
-            image.Source = bmp;
+            OnError(image, ex, AnimationErrorKind.Loading);
         }
     }
+
+    private static void SetStaticImageCore(Image image, Stream stream)
+    {
+        stream.Seek(0, SeekOrigin.Begin);
+        var bmp = new BitmapImage();
+        bmp.BeginInit();
+        bmp.CacheOption = BitmapCacheOption.OnLoad;
+        bmp.StreamSource = stream;
+        bmp.EndInit();
+        image.Source = bmp;
+    }
 }

+ 8 - 9
src/XamlAnimatedGif/AnimationCompletedEventArgs.cs

@@ -1,14 +1,13 @@
 using System.Windows;
 
-namespace XamlAnimatedGif
-{
-    public delegate void AnimationCompletedEventHandler(DependencyObject d, AnimationCompletedEventArgs e);
+namespace XamlAnimatedGif;
+
+public delegate void AnimationCompletedEventHandler(DependencyObject d, AnimationCompletedEventArgs e);
 
-    public class AnimationCompletedEventArgs : RoutedEventArgs
+public class AnimationCompletedEventArgs : RoutedEventArgs
+{
+    public AnimationCompletedEventArgs(object source)
+        : base(AnimationBehavior.AnimationCompletedEvent, source)
     {
-        public AnimationCompletedEventArgs(object source)
-            : base(AnimationBehavior.AnimationCompletedEvent, source)
-        {
-        }
     }
-}
+}

+ 18 - 19
src/XamlAnimatedGif/AnimationErrorEventArgs.cs

@@ -1,27 +1,26 @@
 using System;
 using System.Windows;
 
-namespace XamlAnimatedGif
-{
-    public delegate void AnimationErrorEventHandler(DependencyObject d, AnimationErrorEventArgs e);
-
-    public class AnimationErrorEventArgs : RoutedEventArgs
-    {
-        public AnimationErrorEventArgs(object source, Exception exception, AnimationErrorKind kind)
-            : base(AnimationBehavior.ErrorEvent, source)
-        {
-            Exception = exception;
-            Kind = kind;
-        }
+namespace XamlAnimatedGif;
 
-        public Exception Exception { get; }
-
-        public AnimationErrorKind Kind { get; }
-    }
+public delegate void AnimationErrorEventHandler(DependencyObject d, AnimationErrorEventArgs e);
 
-    public enum AnimationErrorKind
+public class AnimationErrorEventArgs : RoutedEventArgs
+{
+    public AnimationErrorEventArgs(object source, Exception exception, AnimationErrorKind kind)
+        : base(AnimationBehavior.ErrorEvent, source)
     {
-        Loading,
-        Rendering
+        Exception = exception;
+        Kind = kind;
     }
+
+    public Exception Exception { get; }
+
+    public AnimationErrorKind Kind { get; }
 }
+
+public enum AnimationErrorKind
+{
+    Loading,
+    Rendering
+}

+ 8 - 9
src/XamlAnimatedGif/AnimationStartedEventArgs.cs

@@ -1,14 +1,13 @@
 using System.Windows;
 
-namespace XamlAnimatedGif
-{
-    public delegate void AnimationStartedEventHandler(DependencyObject d, AnimationStartedEventArgs e);
+namespace XamlAnimatedGif;
+
+public delegate void AnimationStartedEventHandler(DependencyObject d, AnimationStartedEventArgs e);
 
-    public class AnimationStartedEventArgs : RoutedEventArgs
+public class AnimationStartedEventArgs : RoutedEventArgs
+{
+    public AnimationStartedEventArgs(object source)
+        : base(AnimationBehavior.AnimationStartedEvent, source)
     {
-        public AnimationStartedEventArgs(object source)
-            : base(AnimationBehavior.AnimationStartedEvent, source)
-        {
-        }
     }
-}
+}

+ 505 - 534
src/XamlAnimatedGif/Animator.cs

@@ -13,699 +13,670 @@ using XamlAnimatedGif.Decoding;
 using XamlAnimatedGif.Decompression;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif
+namespace XamlAnimatedGif;
+
+public abstract class Animator : DependencyObject, IDisposable
 {
-    public abstract class Animator : DependencyObject, IDisposable
-    {
-        private static readonly Task CompletedTask = Task.FromResult(0);
-        private readonly Stream _sourceStream;
-        private readonly Uri _sourceUri;
-        private readonly bool _isSourceStreamOwner;
-        private readonly GifDataStream _metadata;
-        private readonly Dictionary<int, GifPalette> _palettes;
-        private readonly WriteableBitmap _bitmap;
-        private readonly int _stride;
-        private readonly byte[] _previousBackBuffer;
-        private readonly byte[] _indexStreamBuffer;
-        private readonly TimingManager _timingManager;
-        private readonly bool _cacheFrameDataInMemory;
-        private readonly byte[][][] _cachedFrameBytes;
-        private TaskCompletionSource<bool> _frameLoadedEvent;
-        private readonly object _lockObject;
-
-        #region Constructor and factory methods
-
-        internal Animator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior,
-            bool cacheFrameDataInMemory, CancellationToken cancellationToken)
+    private readonly Stream _sourceStream;
+    private readonly Uri _sourceUri;
+    private readonly bool _isSourceStreamOwner;
+    private readonly GifDataStream _metadata;
+    private readonly Dictionary<int, GifPalette> _palettes;
+    private readonly WriteableBitmap _bitmap;
+    private readonly int _stride;
+    private readonly byte[] _previousBackBuffer;
+    private readonly byte[] _indexStreamBuffer;
+    private readonly TimingManager _timingManager;
+    private readonly bool _cacheFrameDataInMemory;
+    private readonly byte[][] _cachedFrameBytes;
+    private readonly Task _loadFramesDataTask;
+
+    #region Constructor and factory methods
+
+    internal Animator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior,
+        bool cacheFrameDataInMemory)
+    {
+        _sourceStream = sourceStream;
+        _sourceUri = sourceUri;
+        _isSourceStreamOwner = sourceUri != null; // stream opened from URI, should close it
+        _metadata = metadata;
+        _palettes = CreatePalettes(metadata);
+        _bitmap = CreateBitmap(metadata);
+        var desc = metadata.Header.LogicalScreenDescriptor;
+        _stride = 4 * ((desc.Width * 32 + 31) / 32);
+        _previousBackBuffer = new byte[desc.Height * _stride];
+        _indexStreamBuffer = CreateIndexStreamBuffer(metadata, _sourceStream);
+        _timingManager = CreateTimingManager(metadata, repeatBehavior);
+
+        _cacheFrameDataInMemory = cacheFrameDataInMemory;
+
+        if (cacheFrameDataInMemory)
         {
-            _sourceStream = sourceStream;
-            _sourceUri = sourceUri;
-            _isSourceStreamOwner = sourceUri != null; // stream opened from URI, should close it
-            _metadata = metadata;
-            _palettes = CreatePalettes(metadata);
-            _bitmap = CreateBitmap(metadata);
-            var desc = metadata.Header.LogicalScreenDescriptor;
-            _stride = 4 * ((desc.Width * 32 + 31) / 32);
-            _previousBackBuffer = new byte[desc.Height * _stride];
-            _indexStreamBuffer = CreateIndexStreamBuffer(metadata, _sourceStream);
-            _timingManager = CreateTimingManager(metadata, repeatBehavior);
-
-            _cacheFrameDataInMemory = cacheFrameDataInMemory;
-
-            if (cacheFrameDataInMemory)
-            {
-                _lockObject = new object();
-                _frameLoadedEvent = new TaskCompletionSource<bool>();
-                _cachedFrameBytes = new byte[_metadata.Frames.Count][][];
-                Task.Run(() => LoadFrames(cancellationToken));
-            }
+            _cachedFrameBytes = new byte[_metadata.Frames.Count][];
+            _loadFramesDataTask = Task.Run(LoadFrames);
         }
+    }
 
-        private async Task LoadFrames(CancellationToken cancellationToken)
+    private async Task LoadFrames()
+    {
+        var biggestFrameSize = 0L;
+        for (var frameIndex = 0; frameIndex < _metadata.Frames.Count; frameIndex++)
         {
-            var biggestFrameSize = 0L;
-            for (var frameIndex = 0; frameIndex < _metadata.Frames.Count; frameIndex++)
-            {
-                var startPosition = _metadata.Frames[frameIndex].ImageData.CompressedDataStartOffset;
-                var endPosition = _metadata.Frames.Count == frameIndex + 1
-                    ? _sourceStream.Length
-                    : _metadata.Frames[frameIndex + 1].ImageData.CompressedDataStartOffset - 1;
-                var size = endPosition - startPosition;
-                biggestFrameSize = Math.Max(size, biggestFrameSize);
-            }
-
-            byte[] indexCompressedBytes = new byte[biggestFrameSize];
-            try
-            {
-                for (var frameIndex = 0; frameIndex < _metadata.Frames.Count; frameIndex++)
-                {
-                    if (cancellationToken.IsCancellationRequested)
-                    {
-                        _frameLoadedEvent.SetCanceled();
-                        _frameLoadedEvent = null;
-                        return;
-                    }
-                    var frame = _metadata.Frames[frameIndex];
-                    var frameDesc = _metadata.Frames[frameIndex].Descriptor;
-                    await GetIndexBytesAsync(frameIndex, indexCompressedBytes);
-                    using var indexDecompressedStream =
-                        new LzwDecompressStream(indexCompressedBytes, frame.ImageData.LzwMinimumCodeSize);
-                    _cachedFrameBytes[frameIndex] = new byte[frame.Descriptor.Height][];
-                    for (var row = 0; row < frame.Descriptor.Height; row++)
-                    {
-                        _cachedFrameBytes[frameIndex][row] = new byte[frameDesc.Width];
-                        await indexDecompressedStream.ReadAllAsync(_cachedFrameBytes[frameIndex][row], 0, frameDesc.Width);
-                    }
-                    NotifyOfFrameLoaded();
-                }
-            }
-            finally
-            {
-                _frameLoadedEvent?.SetResult(true);
-                _frameLoadedEvent = null;
-            }
+            var startPosition = _metadata.Frames[frameIndex].ImageData.CompressedDataStartOffset;
+            var endPosition = _metadata.Frames.Count == frameIndex + 1
+                ? _sourceStream.Length
+                : _metadata.Frames[frameIndex + 1].ImageData.CompressedDataStartOffset - 1;
+            var size = endPosition - startPosition;
+            biggestFrameSize = Math.Max(size, biggestFrameSize);
         }
 
-        internal static async Task<TAnimator> CreateAsyncCore<TAnimator>(
-            Uri sourceUri,
-            IProgress<int> progress,
-            Func<Stream, GifDataStream, TAnimator> create)
-            where TAnimator : Animator
+        var indexCompressedBytes = new byte[biggestFrameSize];
+        for (var frameIndex = 0; frameIndex < _metadata.Frames.Count; frameIndex++)
         {
-            var stream = await UriLoader.GetStreamFromUriAsync(sourceUri, progress);
-            try
-            {
-                // ReSharper disable once AccessToDisposedClosure
-                return await CreateAsyncCore(stream, metadata => create(stream, metadata));
-            }
-            catch
-            {
-                stream?.Dispose();
-                throw;
-            }
+            var frame = _metadata.Frames[frameIndex];
+            var frameDesc = _metadata.Frames[frameIndex].Descriptor;
+            await GetIndexBytesAsync(frameIndex, indexCompressedBytes);
+            await using var indexDecompressedStream =
+                new LzwDecompressStream(indexCompressedBytes, frame.ImageData.LzwMinimumCodeSize);
+            _cachedFrameBytes[frameIndex] = new byte[frameDesc.Width * frameDesc.Height];
+
+            await indexDecompressedStream.ReadAllAsync(_cachedFrameBytes[frameIndex], 0,
+                frameDesc.Width * frameDesc.Height);
         }
+    }
 
-        internal static async Task<TAnimator> CreateAsyncCore<TAnimator>(
-            Stream sourceStream,
-            Func<GifDataStream, TAnimator> create)
-            where TAnimator : Animator
+    internal static async Task<TAnimator> CreateAsyncCore<TAnimator>(
+        Uri sourceUri,
+        IProgress<int> progress,
+        Func<Stream, GifDataStream, TAnimator> create)
+        where TAnimator : Animator
+    {
+        var stream = await UriLoader.GetStreamFromUriAsync(sourceUri);
+        try
+        {
+            // ReSharper disable once AccessToDisposedClosure
+            return await CreateAsyncCore(stream, metadata => create(stream, metadata));
+        }
+        catch
         {
-            if (!sourceStream.CanSeek)
-                throw new ArgumentException("The stream is not seekable");
-            sourceStream.Seek(0, SeekOrigin.Begin);
-            var metadata = await GifDataStream.ReadAsync(sourceStream);
-            return create(metadata);
+            stream?.Dispose();
+            throw;
         }
+    }
 
-        #endregion Constructor and factory methods
+    internal static async Task<TAnimator> CreateAsyncCore<TAnimator>(
+        Stream sourceStream,
+        Func<GifDataStream, TAnimator> create)
+        where TAnimator : Animator
+    {
+        if (!sourceStream.CanSeek)
+            throw new ArgumentException("The stream is not seekable");
+        sourceStream.Seek(0, SeekOrigin.Begin);
+        var metadata = await GifDataStream.ReadAsync(sourceStream);
+        return create(metadata);
+    }
+
+    #endregion Constructor and factory methods
 
-        #region Animation
+    #region Animation
 
-        public int FrameCount => _metadata.Frames.Count;
+    public int FrameCount => _metadata.Frames.Count;
 
-        private bool _isStarted;
-        private CancellationTokenSource _cancellationTokenSource;
+    private bool _isStarted;
+    private CancellationTokenSource _cancellationTokenSource;
 
-        public async void Play()
+    public async void Play()
+    {
+        try
         {
-            try
+            if (_timingManager.IsComplete)
             {
-                if (_timingManager.IsComplete)
-                {
-                    _timingManager.Reset();
-                    _isStarted = false;
-                }
-
-                if (!_isStarted)
-                {
-                    _cancellationTokenSource?.Dispose();
-                    _cancellationTokenSource = new CancellationTokenSource();
-                    _isStarted = true;
-                    OnAnimationStarted();
-                    if (_timingManager.IsPaused)
-                        _timingManager.Resume();
-                    await RunAsync(_cancellationTokenSource.Token);
-                }
-                else if (_timingManager.IsPaused)
-                {
-                    _timingManager.Resume();
-                }
+                _timingManager.Reset();
+                _isStarted = false;
             }
-            catch (OperationCanceledException)
+
+            if (!_isStarted)
             {
+                _cancellationTokenSource?.Dispose();
+                _cancellationTokenSource = new CancellationTokenSource();
+                _isStarted = true;
+                OnAnimationStarted();
+                if (_timingManager.IsPaused)
+                    _timingManager.Resume();
+                await RunAsync(_cancellationTokenSource.Token);
             }
-            catch (Exception ex)
+            else if (_timingManager.IsPaused)
             {
-                // ignore errors that might occur during Dispose
-                if (!_disposing)
-                    OnError(ex, AnimationErrorKind.Rendering);
+                _timingManager.Resume();
             }
         }
-
-        private int _frameIndex;
-
-        private async Task RunAsync(CancellationToken cancellationToken)
+        catch (OperationCanceledException)
         {
-            while (true)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-                var timing = _timingManager.NextAsync(cancellationToken);
-                var rendering = RenderFrameAsync(CurrentFrameIndex, cancellationToken);
-                await Task.WhenAll(timing, rendering);
-                if (!timing.Result)
-                    break;
-                CurrentFrameIndex = (CurrentFrameIndex + 1) % FrameCount;
-            }
         }
-
-        public void Pause()
+        catch (Exception ex)
         {
-            _timingManager.Pause();
+            // ignore errors that might occur during Dispose
+            if (!_disposing)
+                OnError(ex, AnimationErrorKind.Rendering);
         }
+    }
 
-        public bool IsPaused => _timingManager.IsPaused;
+    private int _frameIndex;
 
-        public bool IsComplete
+    private async Task RunAsync(CancellationToken cancellationToken)
+    {
+        if (_loadFramesDataTask != null)
+            await _loadFramesDataTask;
+        while (true)
         {
-            get
-            {
-                if (_isStarted)
-                    return _timingManager.IsComplete;
-                return false;
-            }
+            cancellationToken.ThrowIfCancellationRequested();
+            var timing = _timingManager.NextAsync(cancellationToken);
+            var rendering = RenderFrameAsync(CurrentFrameIndex, cancellationToken);
+            await Task.WhenAll(timing, rendering);
+            if (!timing.Result)
+                break;
+            CurrentFrameIndex = (CurrentFrameIndex + 1) % FrameCount;
         }
+    }
+
+    public void Pause()
+    {
+        _timingManager.Pause();
+    }
 
-        public event EventHandler CurrentFrameChanged;
+    public bool IsPaused => _timingManager.IsPaused;
 
-        protected virtual void OnCurrentFrameChanged()
+    public bool IsComplete
+    {
+        get
         {
-            CurrentFrameChanged?.Invoke(this, EventArgs.Empty);
+            if (_isStarted)
+                return _timingManager.IsComplete;
+            return false;
         }
+    }
 
-        public event EventHandler<AnimationStartedEventArgs> AnimationStarted;
+    public event EventHandler CurrentFrameChanged;
 
-        protected virtual void OnAnimationStarted()
+    protected virtual void OnCurrentFrameChanged()
+    {
+        CurrentFrameChanged?.Invoke(this, EventArgs.Empty);
+    }
+
+    public event EventHandler<AnimationStartedEventArgs> AnimationStarted;
+
+    protected virtual void OnAnimationStarted()
+    {
+        AnimationStarted?.Invoke(this, new AnimationStartedEventArgs(AnimationSource));
+    }
+
+    public event EventHandler<AnimationCompletedEventArgs> AnimationCompleted;
+
+    protected virtual void OnAnimationCompleted()
+    {
+        AnimationCompleted?.Invoke(this, new AnimationCompletedEventArgs(AnimationSource));
+    }
+
+    public event EventHandler<AnimationErrorEventArgs> Error;
+
+    protected virtual void OnError(Exception ex, AnimationErrorKind kind)
+    {
+        Error?.Invoke(this, new AnimationErrorEventArgs(AnimationSource, ex, kind));
+    }
+
+    public int CurrentFrameIndex
+    {
+        get => _frameIndex;
+        private set
         {
-            AnimationStarted?.Invoke(this, new AnimationStartedEventArgs(AnimationSource));
+            _frameIndex = value;
+            OnCurrentFrameChanged();
         }
+    }
 
-        public event EventHandler<AnimationCompletedEventArgs> AnimationCompleted;
+    private TimingManager CreateTimingManager(GifDataStream metadata, RepeatBehavior repeatBehavior)
+    {
+        var actualRepeatBehavior = GetActualRepeatBehavior(metadata, repeatBehavior);
 
-        protected virtual void OnAnimationCompleted()
+        var manager = new TimingManager(actualRepeatBehavior);
+        foreach (var frame in metadata.Frames)
         {
-            AnimationCompleted?.Invoke(this, new AnimationCompletedEventArgs(AnimationSource));
+            manager.Add(GetFrameDelay(frame));
         }
 
-        public event EventHandler<AnimationErrorEventArgs> Error;
+        manager.Completed += TimingManagerCompleted;
+        return manager;
+    }
+
+    private static RepeatBehavior GetActualRepeatBehavior(GifDataStream metadata, RepeatBehavior repeatBehavior)
+    {
+        return repeatBehavior == default
+            ? GetRepeatBehaviorFromGif(metadata)
+            : repeatBehavior;
+    }
+
+    protected abstract RepeatBehavior GetSpecifiedRepeatBehavior();
+
+    private void TimingManagerCompleted(object sender, EventArgs e)
+    {
+        OnAnimationCompleted();
+    }
+
+    #endregion Animation
 
-        protected virtual void OnError(Exception ex, AnimationErrorKind kind)
+    #region Rendering
+
+    private static WriteableBitmap CreateBitmap(GifDataStream metadata)
+    {
+        var desc = metadata.Header.LogicalScreenDescriptor;
+        var bitmap = new WriteableBitmap(desc.Width, desc.Height, 96, 96, PixelFormats.Bgra32, null);
+        return bitmap;
+    }
+
+    private static Dictionary<int, GifPalette> CreatePalettes(GifDataStream metadata)
+    {
+        var palettes = new Dictionary<int, GifPalette>();
+        Color[] globalColorTable = null;
+        if (metadata.Header.LogicalScreenDescriptor.HasGlobalColorTable)
         {
-            Error?.Invoke(this, new AnimationErrorEventArgs(AnimationSource, ex, kind));
+            globalColorTable =
+                metadata.GlobalColorTable
+                    .Select(gc => Color.FromArgb(0xFF, gc.R, gc.G, gc.B))
+                    .ToArray();
         }
 
-        public int CurrentFrameIndex
+        for (var i = 0; i < metadata.Frames.Count; i++)
         {
-            get => _frameIndex;
-            private set
+            var frame = metadata.Frames[i];
+            var colorTable = globalColorTable;
+            if (frame.Descriptor.HasLocalColorTable)
             {
-                _frameIndex = value;
-                OnCurrentFrameChanged();
+                colorTable =
+                    frame.LocalColorTable
+                        .Select(gc => Color.FromArgb(0xFF, gc.R, gc.G, gc.B))
+                        .ToArray();
             }
-        }
 
-        private TimingManager CreateTimingManager(GifDataStream metadata, RepeatBehavior repeatBehavior)
-        {
-            var actualRepeatBehavior = GetActualRepeatBehavior(metadata, repeatBehavior);
-
-            var manager = new TimingManager(actualRepeatBehavior);
-            foreach (var frame in metadata.Frames)
+            int? transparencyIndex = null;
+            var gce = frame.GraphicControl;
+            if (gce is { HasTransparency: true })
             {
-                manager.Add(GetFrameDelay(frame));
+                transparencyIndex = gce.TransparencyIndex;
             }
 
-            manager.Completed += TimingManagerCompleted;
-            return manager;
+            palettes[i] = new GifPalette(transparencyIndex, colorTable);
         }
 
-        private static RepeatBehavior GetActualRepeatBehavior(GifDataStream metadata, RepeatBehavior repeatBehavior)
-        {
-            return repeatBehavior == default
-                    ? GetRepeatBehaviorFromGif(metadata)
-                    : repeatBehavior;
-        }
+        return palettes;
+    }
 
-        protected abstract RepeatBehavior GetSpecifiedRepeatBehavior();
+    private static byte[] CreateIndexStreamBuffer(GifDataStream metadata, Stream stream)
+    {
+        // Find the size of the largest frame pixel data
+        // (ignoring the fact that we include the next frame's header)
 
-        private void TimingManagerCompleted(object sender, EventArgs e)
+        var lastSize = stream.Length - metadata.Frames.Last().ImageData.CompressedDataStartOffset;
+        var maxSize = lastSize;
+        if (metadata.Frames.Count > 1)
         {
-            OnAnimationCompleted();
+            var sizes = metadata.Frames.Zip(metadata.Frames.Skip(1),
+                (f1, f2) => f2.ImageData.CompressedDataStartOffset - f1.ImageData.CompressedDataStartOffset);
+            maxSize = Math.Max(sizes.Max(), lastSize);
         }
 
-        #endregion Animation
+        // Need 4 extra bytes so that BitReader doesn't need to check the size for every read
+        return new byte[maxSize + 4];
+    }
+
+    private int _previousFrameIndex;
+    private GifFrame _previousFrame;
 
-        #region Rendering
+    private async Task RenderFrameAsync(int frameIndex, CancellationToken cancellationToken)
+    {
+        if (frameIndex < 0)
+            return;
+
+        var frame = _metadata.Frames[frameIndex];
+        var desc = frame.Descriptor;
+        var rect = GetFixedUpFrameRect(desc);
 
-        private static WriteableBitmap CreateBitmap(GifDataStream metadata)
+        Stream indexStream = null;
+        if (!_cacheFrameDataInMemory)
         {
-            var desc = metadata.Header.LogicalScreenDescriptor;
-            var bitmap = new WriteableBitmap(desc.Width, desc.Height, 96, 96, PixelFormats.Bgra32, null);
-            return bitmap;
+            indexStream = await GetIndexStreamAsync(frame, cancellationToken);
         }
 
-        private static Dictionary<int, GifPalette> CreatePalettes(GifDataStream metadata)
+        await using (indexStream)
+        using (_bitmap.LockInScope())
         {
-            var palettes = new Dictionary<int, GifPalette>();
-            Color[] globalColorTable = null;
-            if (metadata.Header.LogicalScreenDescriptor.HasGlobalColorTable)
+            if (frameIndex < _previousFrameIndex)
+                ClearArea(_metadata.Header.LogicalScreenDescriptor);
+            else
+                DisposePreviousFrame(frame);
+
+            var bufferLength = 4 * rect.Width;
+            byte[] indexBuffer;
+            var lineBuffer = new byte[bufferLength];
+
+            var palette = _palettes[frameIndex];
+            var transparencyIndex = palette.TransparencyIndex ?? -1;
+
+            var rows = desc.Interlace
+                ? InterlacedRows(rect.Height)
+                : NormalRows(rect.Height);
+
+            if (!_cacheFrameDataInMemory)
             {
-                globalColorTable =
-                    metadata.GlobalColorTable
-                        .Select(gc => Color.FromArgb(0xFF, gc.R, gc.G, gc.B))
-                        .ToArray();
+                indexBuffer = new byte[desc.Width * desc.Height];
+                await indexStream.ReadAllAsync(indexBuffer, 0, indexBuffer.Length, cancellationToken);
+            }
+            else
+            {
+                indexBuffer = _cachedFrameBytes[frameIndex];
             }
 
-            for (int i = 0; i < metadata.Frames.Count; i++)
+            foreach (var y in rows)
             {
-                var frame = metadata.Frames[i];
-                var colorTable = globalColorTable;
-                if (frame.Descriptor.HasLocalColorTable)
+                var offset = (desc.Top + y) * _stride + desc.Left * 4;
+
+                if (transparencyIndex >= 0)
                 {
-                    colorTable =
-                        frame.LocalColorTable
-                            .Select(gc => Color.FromArgb(0xFF, gc.R, gc.G, gc.B))
-                            .ToArray();
+                    CopyFromBitmap(lineBuffer, _bitmap, offset, bufferLength);
                 }
 
-                int? transparencyIndex = null;
-                var gce = frame.GraphicControl;
-                if (gce is { HasTransparency: true })
+                for (var x = 0; x < rect.Width; x++)
                 {
-                    transparencyIndex = gce.TransparencyIndex;
+                    var index = indexBuffer[x + y * desc.Width];
+                    var i = 4 * x;
+                    if (index != transparencyIndex)
+                    {
+                        WriteColor(lineBuffer, palette[index], i);
+                    }
                 }
 
-                palettes[i] = new GifPalette(transparencyIndex, colorTable);
+                CopyToBitmap(lineBuffer, _bitmap, offset, bufferLength);
             }
 
-            return palettes;
+            _bitmap.AddDirtyRect(rect);
         }
 
-        private static byte[] CreateIndexStreamBuffer(GifDataStream metadata, Stream stream)
-        {
-            // Find the size of the largest frame pixel data
-            // (ignoring the fact that we include the next frame's header)
+        _previousFrame = frame;
+        _previousFrameIndex = frameIndex;
+    }
 
-            long lastSize = stream.Length - metadata.Frames.Last().ImageData.CompressedDataStartOffset;
-            long maxSize = lastSize;
-            if (metadata.Frames.Count > 1)
-            {
-                var sizes = metadata.Frames.Zip(metadata.Frames.Skip(1),
-                    (f1, f2) => f2.ImageData.CompressedDataStartOffset - f1.ImageData.CompressedDataStartOffset);
-                maxSize = Math.Max(sizes.Max(), lastSize);
+    private static IEnumerable<int> NormalRows(int height)
+    {
+        return Enumerable.Range(0, height);
+    }
+
+    private static IEnumerable<int> InterlacedRows(int height)
+    {
+        /*
+         * 4 passes:
+         * Pass 1: rows 0, 8, 16, 24...
+         * Pass 2: rows 4, 12, 20, 28...
+         * Pass 3: rows 2, 6, 10, 14...
+         * Pass 4: rows 1, 3, 5, 7...
+         * */
+        var passes = new[]
+        {
+            new { Start = 0, Step = 8 },
+            new { Start = 4, Step = 8 },
+            new { Start = 2, Step = 4 },
+            new { Start = 1, Step = 2 }
+        };
+        foreach (var pass in passes)
+        {
+            var y = pass.Start;
+            while (y < height)
+            {
+                yield return y;
+                y += pass.Step;
             }
-            // Need 4 extra bytes so that BitReader doesn't need to check the size for every read
-            return new byte[maxSize + 4];
         }
+    }
 
-        private int _previousFrameIndex;
-        private GifFrame _previousFrame;
+    private static void CopyToBitmap(byte[] buffer, WriteableBitmap bitmap, int offset, int length)
+    {
+        Marshal.Copy(buffer, 0, bitmap.BackBuffer + offset, length);
+    }
 
-        private async Task RenderFrameAsync(int frameIndex, CancellationToken cancellationToken)
-        {
-            await GetWaitLoadSingleFrameTask();
-            if (frameIndex < 0)
-                return;
+    private static void CopyFromBitmap(byte[] buffer, WriteableBitmap bitmap, int offset, int length)
+    {
+        Marshal.Copy(bitmap.BackBuffer + offset, buffer, 0, length);
+    }
 
-            var frame = _metadata.Frames[frameIndex];
-            var desc = frame.Descriptor;
-            var rect = GetFixedUpFrameRect(desc);
+    private static void WriteColor(byte[] lineBuffer, Color color, int startIndex)
+    {
+        lineBuffer[startIndex] = color.B;
+        lineBuffer[startIndex + 1] = color.G;
+        lineBuffer[startIndex + 2] = color.R;
+        lineBuffer[startIndex + 3] = color.A;
+    }
 
-            Stream indexStream = null;
-            if (!_cacheFrameDataInMemory)
-            {
-                indexStream = await GetIndexStreamAsync(frame, cancellationToken);
-            }
-            using (indexStream)
-            using (_bitmap.LockInScope())
+    private void DisposePreviousFrame(GifFrame currentFrame)
+    {
+        var pgce = _previousFrame?.GraphicControl;
+        if (pgce != null)
+        {
+            switch (pgce.DisposalMethod)
             {
-                if (frameIndex < _previousFrameIndex)
-                    ClearArea(_metadata.Header.LogicalScreenDescriptor);
-                else
-                    DisposePreviousFrame(frame);
-
-                int bufferLength = 4 * rect.Width;
-                byte[] indexBuffer = new byte[desc.Width];
-                byte[] lineBuffer = new byte[bufferLength];
-
-                var palette = _palettes[frameIndex];
-                int transparencyIndex = palette.TransparencyIndex ?? -1;
-
-                var rows = desc.Interlace
-                    ? InterlacedRows(rect.Height)
-                    : NormalRows(rect.Height);
-
-                foreach (int y in rows)
-                {
-                    if (!_cacheFrameDataInMemory)
+                case GifFrameDisposalMethod.None:
+                case GifFrameDisposalMethod.DoNotDispose:
                     {
-                        await indexStream.ReadAllAsync(indexBuffer, 0, desc.Width, cancellationToken);
+                        // Leave previous frame in place
+                        break;
                     }
-                    else
+                case GifFrameDisposalMethod.RestoreBackground:
                     {
-                        indexBuffer = _cachedFrameBytes[frameIndex][y];
+                        ClearArea(GetFixedUpFrameRect(_previousFrame.Descriptor));
+                        break;
                     }
-
-                    int offset = (desc.Top + y) * _stride + desc.Left * 4;
-
-                    if (transparencyIndex >= 0)
+                case GifFrameDisposalMethod.RestorePrevious:
                     {
-                        CopyFromBitmap(lineBuffer, _bitmap, offset, bufferLength);
+                        CopyToBitmap(_previousBackBuffer, _bitmap, 0, _previousBackBuffer.Length);
+                        var desc = _metadata.Header.LogicalScreenDescriptor;
+                        var rect = new Int32Rect(0, 0, desc.Width, desc.Height);
+                        _bitmap.AddDirtyRect(rect);
+                        break;
                     }
-
-                    for (int x = 0; x < rect.Width; x++)
-                    {
-                        byte index = indexBuffer[x];
-                        int i = 4 * x;
-                        if (index != transparencyIndex)
-                        {
-                            WriteColor(lineBuffer, palette[index], i);
-                        }
-                    }
-                    CopyToBitmap(lineBuffer, _bitmap, offset, bufferLength);
-                }
-                _bitmap.AddDirtyRect(rect);
             }
-
-            _previousFrame = frame;
-            _previousFrameIndex = frameIndex;
         }
 
-        private void NotifyOfFrameLoaded()
+        var gce = currentFrame.GraphicControl;
+        if (gce is { DisposalMethod: GifFrameDisposalMethod.RestorePrevious })
         {
-            lock (_lockObject)
-            {
-                _frameLoadedEvent.SetResult(true);
-                _frameLoadedEvent = new TaskCompletionSource<bool>();
-            }
+            CopyFromBitmap(_previousBackBuffer, _bitmap, 0, _previousBackBuffer.Length);
         }
+    }
 
-        private Task GetWaitLoadSingleFrameTask()
-        {
-            if (_frameLoadedEvent == null)
-                return CompletedTask; // avoiding lock statement if loading frames completed
-            lock (_lockObject)
-            {
-                return _frameLoadedEvent.Task ?? CompletedTask;
-            }
-        }
+    private void ClearArea(IGifRect rect)
+    {
+        ClearArea(new Int32Rect(rect.Left, rect.Top, rect.Width, rect.Height));
+    }
 
-        private static IEnumerable<int> NormalRows(int height)
+    private void ClearArea(Int32Rect rect)
+    {
+        var bufferLength = 4 * rect.Width;
+        var lineBuffer = new byte[bufferLength];
+        for (var y = 0; y < rect.Height; y++)
         {
-            return Enumerable.Range(0, height);
+            var offset = (rect.Y + y) * _stride + 4 * rect.X;
+            CopyToBitmap(lineBuffer, _bitmap, offset, bufferLength);
         }
 
-        private static IEnumerable<int> InterlacedRows(int height)
-        {
-            /*
-             * 4 passes:
-             * Pass 1: rows 0, 8, 16, 24...
-             * Pass 2: rows 4, 12, 20, 28...
-             * Pass 3: rows 2, 6, 10, 14...
-             * Pass 4: rows 1, 3, 5, 7...
-             * */
-            var passes = new[]
-            {
-                new { Start = 0, Step = 8 },
-                new { Start = 4, Step = 8 },
-                new { Start = 2, Step = 4 },
-                new { Start = 1, Step = 2 }
-            };
-            foreach (var pass in passes)
-            {
-                int y = pass.Start;
-                while (y < height)
-                {
-                    yield return y;
-                    y += pass.Step;
-                }
-            }
-        }
+        _bitmap.AddDirtyRect(new Int32Rect(rect.X, rect.Y, rect.Width, rect.Height));
+    }
 
-        private static void CopyToBitmap(byte[] buffer, WriteableBitmap bitmap, int offset, int length)
+    private async Task<Stream> GetIndexStreamAsync(GifFrame frame, CancellationToken cancellationToken)
+    {
+        var data = frame.ImageData;
+        cancellationToken.ThrowIfCancellationRequested();
+        _sourceStream.Seek(data.CompressedDataStartOffset, SeekOrigin.Begin);
+        using (var ms = new MemoryStream(_indexStreamBuffer))
         {
-            Marshal.Copy(buffer, 0, bitmap.BackBuffer + offset, length);
+            await GifHelpers.CopyDataBlocksToStreamAsync(_sourceStream, ms, cancellationToken)
+                .ConfigureAwait(false);
         }
 
-        private static void CopyFromBitmap(byte[] buffer, WriteableBitmap bitmap, int offset, int length)
-        {
-            Marshal.Copy(bitmap.BackBuffer + offset, buffer, 0, length);
-        }
+        var lzwStream = new LzwDecompressStream(_indexStreamBuffer, data.LzwMinimumCodeSize);
+        return lzwStream;
+    }
 
-        private static void WriteColor(byte[] lineBuffer, Color color, int startIndex)
-        {
-            lineBuffer[startIndex] = color.B;
-            lineBuffer[startIndex + 1] = color.G;
-            lineBuffer[startIndex + 2] = color.R;
-            lineBuffer[startIndex + 3] = color.A;
-        }
+    private async Task GetIndexBytesAsync(int frameIndex, byte[] buffer)
+    {
+        var startPosition = _metadata.Frames[frameIndex].ImageData.CompressedDataStartOffset;
 
-        private void DisposePreviousFrame(GifFrame currentFrame)
-        {
-            var pgce = _previousFrame?.GraphicControl;
-            if (pgce != null)
-            {
-                switch (pgce.DisposalMethod)
-                {
-                    case GifFrameDisposalMethod.None:
-                    case GifFrameDisposalMethod.DoNotDispose:
-                        {
-                            // Leave previous frame in place
-                            break;
-                        }
-                    case GifFrameDisposalMethod.RestoreBackground:
-                        {
-                            ClearArea(GetFixedUpFrameRect(_previousFrame.Descriptor));
-                            break;
-                        }
-                    case GifFrameDisposalMethod.RestorePrevious:
-                        {
-                            CopyToBitmap(_previousBackBuffer, _bitmap, 0, _previousBackBuffer.Length);
-                            var desc = _metadata.Header.LogicalScreenDescriptor;
-                            var rect = new Int32Rect(0, 0, desc.Width, desc.Height);
-                            _bitmap.AddDirtyRect(rect);
-                            break;
-                        }
-                }
-            }
+        _sourceStream.Seek(startPosition, SeekOrigin.Begin);
+        using var memoryStream = new MemoryStream(buffer);
+        await GifHelpers.CopyDataBlocksToStreamAsync(_sourceStream, memoryStream).ConfigureAwait(false);
+    }
 
-            var gce = currentFrame.GraphicControl;
-            if (gce is { DisposalMethod: GifFrameDisposalMethod.RestorePrevious })
-            {
-                CopyFromBitmap(_previousBackBuffer, _bitmap, 0, _previousBackBuffer.Length);
-            }
-        }
+    internal BitmapSource Bitmap => _bitmap;
+
+    #endregion Rendering
+
+    #region Helper methods
 
-        private void ClearArea(IGifRect rect)
+    private static TimeSpan GetFrameDelay(GifFrame frame)
+    {
+        var gce = frame.GraphicControl;
+        if (gce != null)
         {
-            ClearArea(new Int32Rect(rect.Left, rect.Top, rect.Width, rect.Height));
+            if (gce.Delay != 0)
+                return TimeSpan.FromMilliseconds(gce.Delay);
         }
 
-        private void ClearArea(Int32Rect rect)
-        {
-            int bufferLength = 4 * rect.Width;
-            byte[] lineBuffer = new byte[bufferLength];
-            for (int y = 0; y < rect.Height; y++)
-            {
-                int offset = (rect.Y + y) * _stride + 4 * rect.X;
-                CopyToBitmap(lineBuffer, _bitmap, offset, bufferLength);
-            }
+        return TimeSpan.FromMilliseconds(100);
+    }
 
-            _bitmap.AddDirtyRect(new Int32Rect(rect.X, rect.Y, rect.Width, rect.Height));
-        }
+    private static RepeatBehavior GetRepeatBehaviorFromGif(GifDataStream metadata)
+    {
+        if (metadata.RepeatCount == 0)
+            return RepeatBehavior.Forever;
+        return new RepeatBehavior(metadata.RepeatCount);
+    }
 
-        private async Task<Stream> GetIndexStreamAsync(GifFrame frame, CancellationToken cancellationToken)
-        {
-            var data = frame.ImageData;
-            cancellationToken.ThrowIfCancellationRequested();
-            _sourceStream.Seek(data.CompressedDataStartOffset, SeekOrigin.Begin);
-            using (var ms = new MemoryStream(_indexStreamBuffer))
-            {
-                await GifHelpers.CopyDataBlocksToStreamAsync(_sourceStream, ms, cancellationToken).ConfigureAwait(false);
-            }
-            var lzwStream = new LzwDecompressStream(_indexStreamBuffer, data.LzwMinimumCodeSize);
-            return lzwStream;
-        }
+    private Int32Rect GetFixedUpFrameRect(GifImageDescriptor desc)
+    {
+        var width = Math.Min(desc.Width, _bitmap.PixelWidth - desc.Left);
+        var height = Math.Min(desc.Height, _bitmap.PixelHeight - desc.Top);
+        return new Int32Rect(desc.Left, desc.Top, width, height);
+    }
 
-        private async Task GetIndexBytesAsync(int frameIndex, byte[] buffer)
-        {
-            var startPosition = _metadata.Frames[frameIndex].ImageData.CompressedDataStartOffset;
+    #endregion Helper methods
 
-            _sourceStream.Seek(startPosition, SeekOrigin.Begin);
-            using var memoryStream = new MemoryStream(buffer);
-            await GifHelpers.CopyDataBlocksToStreamAsync(_sourceStream, memoryStream).ConfigureAwait(false);
-        }
+    #region Finalizer and Dispose
 
-        internal BitmapSource Bitmap => _bitmap;
+    ~Animator()
+    {
+        Dispose(false);
+    }
 
-        #endregion Rendering
+    public void Dispose()
+    {
+        Dispose(true);
+        GC.SuppressFinalize(this);
+    }
 
-        #region Helper methods
+    private volatile bool _disposing;
+    private bool _disposed;
 
-        private static TimeSpan GetFrameDelay(GifFrame frame)
+    protected virtual void Dispose(bool disposing)
+    {
+        if (!_disposed)
         {
-            var gce = frame.GraphicControl;
-            if (gce != null)
+            _disposing = true;
+            if (_timingManager != null) _timingManager.Completed -= TimingManagerCompleted;
+            _cancellationTokenSource?.Cancel();
+            if (_isSourceStreamOwner)
             {
-                if (gce.Delay != 0)
-                    return TimeSpan.FromMilliseconds(gce.Delay);
+                try
+                {
+                    _sourceStream?.Dispose();
+                }
+                catch
+                {
+                    /* ignored */
+                }
             }
-            return TimeSpan.FromMilliseconds(100);
-        }
 
-        private static RepeatBehavior GetRepeatBehaviorFromGif(GifDataStream metadata)
-        {
-            if (metadata.RepeatCount == 0)
-                return RepeatBehavior.Forever;
-            return new RepeatBehavior(metadata.RepeatCount);
-        }
-
-        private Int32Rect GetFixedUpFrameRect(GifImageDescriptor desc)
-        {
-            int width = Math.Min(desc.Width, _bitmap.PixelWidth - desc.Left);
-            int height = Math.Min(desc.Height, _bitmap.PixelHeight - desc.Top);
-            return new Int32Rect(desc.Left, desc.Top, width, height);
+            _disposed = true;
         }
+    }
 
-        #endregion Helper methods
+    #endregion Finalizer and Dispose
 
-        #region Finalizer and Dispose
+    public override string ToString()
+    {
+        var s = _sourceUri?.ToString() ?? _sourceStream.ToString();
+        return "GIF: " + s;
+    }
 
-        ~Animator()
-        {
-            Dispose(false);
-        }
+    private class GifPalette
+    {
+        private readonly Color[] _colors;
 
-        public void Dispose()
+        public GifPalette(int? transparencyIndex, Color[] colors)
         {
-            Dispose(true);
-            GC.SuppressFinalize(this);
+            TransparencyIndex = transparencyIndex;
+            _colors = colors;
         }
 
-        private volatile bool _disposing;
-        private bool _disposed;
+        public int? TransparencyIndex { get; }
 
-        protected virtual void Dispose(bool disposing)
-        {
-            if (!_disposed)
-            {
-                _disposing = true;
-                if (_timingManager != null) _timingManager.Completed -= TimingManagerCompleted;
-                _cancellationTokenSource?.Cancel();
-                if (_isSourceStreamOwner)
-                {
-                    try
-                    {
-                        _sourceStream?.Dispose();
-                    }
-                    catch
-                    {
-                        /* ignored */
-                    }
-                }
-                _disposed = true;
-            }
-        }
-
-        #endregion Finalizer and Dispose
+        public Color this[int i] => _colors[i];
+    }
 
-        public override string ToString()
+    internal async Task ShowFirstFrameAsync()
+    {
+        try
         {
-            string s = _sourceUri?.ToString() ?? _sourceStream.ToString();
-            return "GIF: " + s;
+            if (_loadFramesDataTask != null)
+                await _loadFramesDataTask;
+            await RenderFrameAsync(0, CancellationToken.None).ConfigureAwait(false);
+            CurrentFrameIndex = 0;
+            _timingManager.Pause();
         }
-
-        private class GifPalette
+        catch (Exception ex)
         {
-            private readonly Color[] _colors;
-
-            public GifPalette(int? transparencyIndex, Color[] colors)
-            {
-                TransparencyIndex = transparencyIndex;
-                _colors = colors;
-            }
-
-            public int? TransparencyIndex { get; }
-
-            public Color this[int i] => _colors[i];
+            OnError(ex, AnimationErrorKind.Rendering);
         }
+    }
 
-        internal async Task ShowFirstFrameAsync()
+    public async void Rewind()
+    {
+        CurrentFrameIndex = 0;
+        var isStopped = _timingManager.IsPaused || _timingManager.IsComplete;
+        _timingManager.Reset();
+        if (isStopped)
         {
+            _timingManager.Pause();
+            _isStarted = false;
             try
             {
                 await RenderFrameAsync(0, CancellationToken.None);
-                CurrentFrameIndex = 0;
-                _timingManager.Pause();
             }
             catch (Exception ex)
             {
                 OnError(ex, AnimationErrorKind.Rendering);
             }
         }
+    }
 
-        public async void Rewind()
-        {
-            CurrentFrameIndex = 0;
-            bool isStopped = _timingManager.IsPaused || _timingManager.IsComplete;
-            _timingManager.Reset();
-            if (isStopped)
-            {
-                _timingManager.Pause();
-                _isStarted = false;
-                try
-                {
-                    await RenderFrameAsync(0, CancellationToken.None);
-                }
-                catch (Exception ex)
-                {
-                    OnError(ex, AnimationErrorKind.Rendering);
-                }
-            }
-        }
-
-        protected abstract object AnimationSource { get; }
+    protected abstract object AnimationSource { get; }
 
-        internal void OnRepeatBehaviorChanged()
-        {
-            if (_timingManager == null)
-                return;
+    internal void OnRepeatBehaviorChanged()
+    {
+        if (_timingManager == null)
+            return;
 
-            var newValue = GetSpecifiedRepeatBehavior();
-            var newActualValue = GetActualRepeatBehavior(_metadata, newValue);
-            if (_timingManager.RepeatBehavior == newActualValue)
-                return;
+        var newValue = GetSpecifiedRepeatBehavior();
+        var newActualValue = GetActualRepeatBehavior(_metadata, newValue);
+        if (_timingManager.RepeatBehavior == newActualValue)
+            return;
 
-            _timingManager.RepeatBehavior = newActualValue;
-            Rewind();
-        }
+        _timingManager.RepeatBehavior = newActualValue;
+        Rewind();
     }
 }

+ 47 - 40
src/XamlAnimatedGif/BrushAnimator.cs

@@ -1,62 +1,69 @@
 using System;
 using System.IO;
-using System.Threading;
 using System.Threading.Tasks;
 using System.Windows.Media;
 using System.Windows.Media.Animation;
 using XamlAnimatedGif.Decoding;
 
-namespace XamlAnimatedGif
+namespace XamlAnimatedGif;
+
+public class BrushAnimator : Animator
 {
-    public class BrushAnimator : Animator
+    private BrushAnimator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior,
+        bool cacheFrameDataInMemory) : base(sourceStream, sourceUri, metadata, repeatBehavior,
+        cacheFrameDataInMemory)
     {
-        private BrushAnimator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior, bool cacheFrameDataInMemory, CancellationToken cancellationToken) : base(sourceStream, sourceUri, metadata, repeatBehavior, cacheFrameDataInMemory, cancellationToken)
-        {
-            Brush = new ImageBrush { ImageSource = Bitmap };
-            RepeatBehavior = _repeatBehavior;
-        }
+        Brush = new ImageBrush { ImageSource = Bitmap };
+        RepeatBehavior = _repeatBehavior;
+    }
 
-        protected override RepeatBehavior GetSpecifiedRepeatBehavior() => RepeatBehavior;
+    protected override RepeatBehavior GetSpecifiedRepeatBehavior()
+    {
+        return RepeatBehavior;
+    }
 
-        protected override object AnimationSource => Brush;
+    protected override object AnimationSource => Brush;
 
-        public ImageBrush Brush { get; }
+    public ImageBrush Brush { get; }
 
-        private RepeatBehavior _repeatBehavior;
+    private RepeatBehavior _repeatBehavior;
 
-        public RepeatBehavior RepeatBehavior
+    public RepeatBehavior RepeatBehavior
+    {
+        get => _repeatBehavior;
+        set
         {
-            get { return _repeatBehavior; }
-            set
-            {
-                _repeatBehavior = value;
-                OnRepeatBehaviorChanged();
-            }
+            _repeatBehavior = value;
+            OnRepeatBehaviorChanged();
         }
+    }
 
-        public static Task<BrushAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior, IProgress<int> progress = null)
-        {
-            return CreateAsync(sourceUri, repeatBehavior, false, CancellationToken.None, progress);
-        }
+    public static Task<BrushAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior,
+        IProgress<int> progress = null)
+    {
+        return CreateAsync(sourceUri, repeatBehavior, false, progress);
+    }
 
-        public static Task<BrushAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior, bool cacheFrameDataInMemory, CancellationToken cancellationToken, IProgress<int> progress = null)
-        {
-            return CreateAsyncCore(
-                sourceUri,
-                progress,
-                (stream, metadata) => new BrushAnimator(stream, sourceUri, metadata, repeatBehavior, cacheFrameDataInMemory, cancellationToken));
-        }
+    public static Task<BrushAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior,
+        bool cacheFrameDataInMemory, IProgress<int> progress = null)
+    {
+        return CreateAsyncCore(
+            sourceUri,
+            progress,
+            (stream, metadata) =>
+                new BrushAnimator(stream, sourceUri, metadata, repeatBehavior, cacheFrameDataInMemory));
+    }
 
-        public static Task<BrushAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior)
-        {
-            return CreateAsync(sourceStream, repeatBehavior, false, CancellationToken.None);
-        }
+    public static Task<BrushAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior)
+    {
+        return CreateAsync(sourceStream, repeatBehavior, false);
+    }
 
-        public static Task<BrushAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior, bool cacheFrameDataInMemory, CancellationToken cancellationToken)
-        {
-            return CreateAsyncCore(
-                sourceStream,
-                metadata => new BrushAnimator(sourceStream, null, metadata, repeatBehavior, cacheFrameDataInMemory, cancellationToken));
-        }
+    public static Task<BrushAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior,
+        bool cacheFrameDataInMemory)
+    {
+        return CreateAsyncCore(
+            sourceStream,
+            metadata => new BrushAnimator(sourceStream, null, metadata, repeatBehavior, cacheFrameDataInMemory));
     }
 }

+ 25 - 30
src/XamlAnimatedGif/CancellationExtensions.cs

@@ -1,39 +1,34 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
+namespace XamlAnimatedGif;
 
-namespace XamlAnimatedGif
+internal static class CancellationExtensions
 {
-    internal static class CancellationExtensions
+    public static async Task WithCancellationToken(this Task task, CancellationToken cancellationToken)
     {
-        public static async Task WithCancellationToken(this Task task, CancellationToken cancellationToken)
-        {
-            await await Task.WhenAny(task, cancellationToken.WhenCanceled());
-        }
+        await await Task.WhenAny(task, cancellationToken.WhenCanceled());
+    }
 
-        public static async Task<T> WithCancellationToken<T>(this Task<T> task, CancellationToken cancellationToken)
-        {
-            var firstTaskToFinish = await Task.WhenAny(task, cancellationToken.WhenCanceled());
-            if (firstTaskToFinish == task)
-                return await task;
+    public static async Task<T> WithCancellationToken<T>(this Task<T> task, CancellationToken cancellationToken)
+    {
+        var firstTaskToFinish = await Task.WhenAny(task, cancellationToken.WhenCanceled());
+        if (firstTaskToFinish == task)
+            return await task;
 
-            await firstTaskToFinish;
+        await firstTaskToFinish;
 
-            // Will never be reached because the previous statement will throw, but necessary to satisfy the compiler
-            throw new OperationCanceledException(cancellationToken);
-        }
+        // Will never be reached because the previous statement will throw, but necessary to satisfy the compiler
+        throw new OperationCanceledException(cancellationToken);
+    }
 
-        public static Task WhenCanceled(this CancellationToken cancellationToken)
+    public static Task WhenCanceled(this CancellationToken cancellationToken)
+    {
+        var tcs = new TaskCompletionSource<int>();
+        var registration = default(CancellationTokenRegistration);
+        registration = cancellationToken.Register(o =>
         {
-            var tcs = new TaskCompletionSource<int>();
-            var registration = default(CancellationTokenRegistration);
-            registration = cancellationToken.Register(o =>
-            {
-                ((TaskCompletionSource<int>)o).TrySetCanceled();
-                // ReSharper disable once AccessToModifiedClosure
-                registration.Dispose();
-            }, tcs);
-            return tcs.Task;
-        }
+            ((TaskCompletionSource<int>)o).TrySetCanceled();
+            // ReSharper disable once AccessToModifiedClosure
+            registration.Dispose();
+        }, tcs);
+        return tcs.Task;
     }
-}
+}

+ 39 - 45
src/XamlAnimatedGif/Decoding/GifApplicationExtension.cs

@@ -1,51 +1,45 @@
-using System;
 using System.IO;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+// label 0xFF
+internal class GifApplicationExtension : GifExtension
 {
-    // label 0xFF
-    internal class GifApplicationExtension : GifExtension
+    internal const int ExtensionLabel = 0xFF;
+
+    public int BlockSize { get; private set; }
+    public string ApplicationIdentifier { get; private set; }
+    public byte[] AuthenticationCode { get; private set; }
+    public byte[] Data { get; private set; }
+
+    private GifApplicationExtension()
+    {
+    }
+
+    internal override GifBlockKind Kind => GifBlockKind.SpecialPurpose;
+
+    internal static async Task<GifApplicationExtension> ReadAsync(Stream stream)
+    {
+        var ext = new GifApplicationExtension();
+        await ext.ReadInternalAsync(stream).ConfigureAwait(false);
+        return ext;
+    }
+
+    private async Task ReadInternalAsync(Stream stream)
     {
-        internal const int ExtensionLabel = 0xFF;
-
-        public int BlockSize { get; private set; }
-        public string ApplicationIdentifier { get; private set; }
-        public byte[] AuthenticationCode { get; private set; }
-        public byte[] Data { get; private set; }
-
-        private GifApplicationExtension()
-        {
-        }
-
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.SpecialPurpose; }
-        }
-
-        internal static async Task<GifApplicationExtension> ReadAsync(Stream stream)
-        {
-            var ext = new GifApplicationExtension();
-            await ext.ReadInternalAsync(stream).ConfigureAwait(false);
-            return ext;
-        }
-
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            // Note: at this point, the label (0xFF) has already been read
-
-            byte[] bytes = new byte[12];
-            await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-            BlockSize = bytes[0]; // should always be 11
-            if (BlockSize != 11)
-                throw GifHelpers.InvalidBlockSizeException("Application Extension", 11, BlockSize);
-
-            ApplicationIdentifier = GifHelpers.GetString(bytes, 1, 8);
-            byte[] authCode = new byte[3];
-            Array.Copy(bytes, 9, authCode, 0, 3);
-            AuthenticationCode = authCode;
-            Data = await GifHelpers.ReadDataBlocksAsync(stream).ConfigureAwait(false);
-        }
+        // Note: at this point, the label (0xFF) has already been read
+
+        var bytes = new byte[12];
+        await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+        BlockSize = bytes[0]; // should always be 11
+        if (BlockSize != 11)
+            throw GifHelpers.InvalidBlockSizeException("Application Extension", 11, BlockSize);
+
+        ApplicationIdentifier = GifHelpers.GetString(bytes, 1, 8);
+        var authCode = new byte[3];
+        Array.Copy(bytes, 9, authCode, 0, 3);
+        AuthenticationCode = authCode;
+        Data = await GifHelpers.ReadDataBlocksAsync(stream).ConfigureAwait(false);
     }
-}
+}

+ 17 - 19
src/XamlAnimatedGif/Decoding/GifBlock.cs

@@ -1,26 +1,24 @@
-using System.Collections.Generic;
 using System.IO;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal abstract class GifBlock
 {
-    internal abstract class GifBlock
+    internal static async Task<GifBlock> ReadAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
     {
-        internal static async Task<GifBlock> ReadAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
+        var blockId = await stream.ReadByteAsync().ConfigureAwait(false);
+        if (blockId < 0)
+            throw new EndOfStreamException();
+        return blockId switch
         {
-            int blockId = await stream.ReadByteAsync().ConfigureAwait(false);
-            if (blockId < 0)
-                throw new EndOfStreamException();
-            return blockId switch
-            {
-                GifExtension.ExtensionIntroducer => await GifExtension.ReadAsync(stream, controlExtensions).ConfigureAwait(false),
-                GifFrame.ImageSeparator => await GifFrame.ReadAsync(stream, controlExtensions).ConfigureAwait(false),
-                GifTrailer.TrailerByte => await GifTrailer.ReadAsync().ConfigureAwait(false),
-                _ => throw GifHelpers.UnknownBlockTypeException(blockId),
-            };
-        }
-
-        internal abstract GifBlockKind Kind { get; }
+            GifExtension.ExtensionIntroducer => await GifExtension.ReadAsync(stream, controlExtensions)
+                .ConfigureAwait(false),
+            GifFrame.ImageSeparator => await GifFrame.ReadAsync(stream, controlExtensions).ConfigureAwait(false),
+            GifTrailer.TrailerByte => await GifTrailer.ReadAsync().ConfigureAwait(false),
+            _ => throw GifHelpers.UnknownBlockTypeException(blockId),
+        };
     }
-}
+
+    internal abstract GifBlockKind Kind { get; }
+}

+ 8 - 9
src/XamlAnimatedGif/Decoding/GifBlockKind.cs

@@ -1,10 +1,9 @@
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal enum GifBlockKind
 {
-    internal enum GifBlockKind
-    {
-        Control,
-        GraphicRendering,
-        SpecialPurpose,
-        Other
-    }
-}
+    Control,
+    GraphicRendering,
+    SpecialPurpose,
+    Other
+}

+ 15 - 16
src/XamlAnimatedGif/Decoding/GifColor.cs

@@ -1,21 +1,20 @@
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal struct GifColor
 {
-    internal struct GifColor
+    internal GifColor(byte r, byte g, byte b)
     {
-        internal GifColor(byte r, byte g, byte b)
-        {
-            R = r;
-            G = g;
-            B = b;
-        }
+        R = r;
+        G = g;
+        B = b;
+    }
 
-        public byte R { get; }
-        public byte G { get; }
-        public byte B { get; }
+    public byte R { get; }
+    public byte G { get; }
+    public byte B { get; }
 
-        public override string ToString()
-        {
-            return $"#{R:x2}{G:x2}{B:x2}";
-        }
+    public override string ToString()
+    {
+        return $"#{R:x2}{G:x2}{B:x2}";
     }
-}
+}

+ 27 - 32
src/XamlAnimatedGif/Decoding/GifCommentExtension.cs

@@ -1,37 +1,32 @@
 using System.IO;
-using System.Threading.Tasks;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifCommentExtension : GifExtension
 {
-    internal class GifCommentExtension : GifExtension
+    internal const int ExtensionLabel = 0xFE;
+
+    public string Text { get; private set; }
+
+    private GifCommentExtension()
     {
-        internal const int ExtensionLabel = 0xFE;
-
-        public string Text { get; private set; }
-
-        private GifCommentExtension()
-        {
-        }
-
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.SpecialPurpose; }
-        }
-
-        internal static async Task<GifCommentExtension> ReadAsync(Stream stream)
-        {
-            var comment = new GifCommentExtension();
-            await comment.ReadInternalAsync(stream).ConfigureAwait(false);
-            return comment;
-        }
-
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            // Note: at this point, the label (0xFE) has already been read
-
-            var bytes = await GifHelpers.ReadDataBlocksAsync(stream).ConfigureAwait(false);
-            if (bytes != null)
-                Text = GifHelpers.GetString(bytes);
-        }
     }
-}
+
+    internal override GifBlockKind Kind => GifBlockKind.SpecialPurpose;
+
+    internal static async Task<GifCommentExtension> ReadAsync(Stream stream)
+    {
+        var comment = new GifCommentExtension();
+        await comment.ReadInternalAsync(stream).ConfigureAwait(false);
+        return comment;
+    }
+
+    private async Task ReadInternalAsync(Stream stream)
+    {
+        // Note: at this point, the label (0xFE) has already been read
+
+        var bytes = await GifHelpers.ReadDataBlocksAsync(stream).ConfigureAwait(false);
+        if (bytes != null)
+            Text = GifHelpers.GetString(bytes);
+    }
+}

+ 72 - 72
src/XamlAnimatedGif/Decoding/GifDataStream.cs

@@ -1,98 +1,98 @@
-using System.Collections.Generic;
 using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifDataStream
 {
-    internal class GifDataStream
+    public GifHeader Header { get; private set; }
+    public GifColor[] GlobalColorTable { get; set; }
+    public IList<GifFrame> Frames { get; set; }
+    public IList<GifExtension> Extensions { get; set; }
+    public ushort RepeatCount { get; set; }
+
+    private GifDataStream()
     {
-        public GifHeader Header { get; private set; }
-        public GifColor[] GlobalColorTable { get; set; }
-        public IList<GifFrame> Frames { get; set; }
-        public IList<GifExtension> Extensions { get; set; }
-        public ushort RepeatCount { get; set; }
+    }
 
-        private GifDataStream()
-        {
-        }
+    internal static async Task<GifDataStream> ReadAsync(Stream stream)
+    {
+        var file = new GifDataStream();
+        await file.ReadInternalAsync(stream).ConfigureAwait(false);
+        return file;
+    }
 
-        internal static async Task<GifDataStream> ReadAsync(Stream stream)
-        {
-            var file = new GifDataStream();
-            await file.ReadInternalAsync(stream).ConfigureAwait(false);
-            return file;
-        }
+    private async Task ReadInternalAsync(Stream stream)
+    {
+        Header = await GifHeader.ReadAsync(stream).ConfigureAwait(false);
 
-        private async Task ReadInternalAsync(Stream stream)
+        if (Header.LogicalScreenDescriptor.HasGlobalColorTable)
         {
-            Header = await GifHeader.ReadAsync(stream).ConfigureAwait(false);
+            GlobalColorTable = await GifHelpers
+                .ReadColorTableAsync(stream, Header.LogicalScreenDescriptor.GlobalColorTableSize)
+                .ConfigureAwait(false);
+        }
 
-            if (Header.LogicalScreenDescriptor.HasGlobalColorTable)
-            {
-                GlobalColorTable = await GifHelpers.ReadColorTableAsync(stream, Header.LogicalScreenDescriptor.GlobalColorTableSize).ConfigureAwait(false);
-            }
-            await ReadFramesAsync(stream).ConfigureAwait(false);
+        await ReadFramesAsync(stream).ConfigureAwait(false);
 
-            var netscapeExtension =
-                            Extensions
-                                .OfType<GifApplicationExtension>()
-                                .FirstOrDefault(GifHelpers.IsNetscapeExtension);
+        var netscapeExtension =
+            Extensions
+                .OfType<GifApplicationExtension>()
+                .FirstOrDefault(GifHelpers.IsNetscapeExtension);
 
-            RepeatCount = netscapeExtension != null
-                ? GifHelpers.GetRepeatCount(netscapeExtension)
-                : (ushort)1;
-        }
+        RepeatCount = netscapeExtension != null
+            ? GifHelpers.GetRepeatCount(netscapeExtension)
+            : (ushort)1;
+    }
 
-        private async Task ReadFramesAsync(Stream stream)
+    private async Task ReadFramesAsync(Stream stream)
+    {
+        var frames = new List<GifFrame>();
+        var controlExtensions = new List<GifExtension>();
+        var specialExtensions = new List<GifExtension>();
+        while (true)
         {
-            List<GifFrame> frames = new List<GifFrame>();
-            List<GifExtension> controlExtensions = new List<GifExtension>();
-            List<GifExtension> specialExtensions = new List<GifExtension>();
-            while (true)
+            try
             {
-                try
-                {
-                    var block = await GifBlock.ReadAsync(stream, controlExtensions).ConfigureAwait(false);
+                var block = await GifBlock.ReadAsync(stream, controlExtensions).ConfigureAwait(false);
 
-                    if (block.Kind == GifBlockKind.GraphicRendering)
-                        controlExtensions = new List<GifExtension>();
+                if (block.Kind == GifBlockKind.GraphicRendering)
+                    controlExtensions = new List<GifExtension>();
 
-                    if (block is GifFrame frame)
-                    {
-                        frames.Add(frame);
-                    }
-                    else if (block is GifExtension extension)
+                if (block is GifFrame frame)
+                {
+                    frames.Add(frame);
+                }
+                else if (block is GifExtension extension)
+                {
+                    switch (extension.Kind)
                     {
-                        switch (extension.Kind)
-                        {
-                            case GifBlockKind.Control:
-                                controlExtensions.Add(extension);
-                                break;
-                            case GifBlockKind.SpecialPurpose:
-                                specialExtensions.Add(extension);
-                                break;
+                        case GifBlockKind.Control:
+                            controlExtensions.Add(extension);
+                            break;
 
-                                // Just discard plain text extensions for now, since we have no use for it
-                        }
-                    }
-                    else if (block is GifTrailer)
-                    {
-                        break;
+                        case GifBlockKind.SpecialPurpose:
+                            specialExtensions.Add(extension);
+                            break;
+
+                            // Just discard plain text extensions for now, since we have no use for it
                     }
                 }
-                // Follow the same approach as Firefox:
-                // If we find extraneous data between blocks, just assume the stream
-                // was successfully terminated if we have some successfully decoded frames
-                // https://dxr.mozilla.org/firefox/source/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp#894-909
-                catch (UnknownBlockTypeException) when (frames.Count > 0)
+                else if (block is GifTrailer)
                 {
                     break;
                 }
             }
-
-            this.Frames = frames.AsReadOnly();
-            this.Extensions = specialExtensions.AsReadOnly();
+            // Follow the same approach as Firefox:
+            // If we find extraneous data between blocks, just assume the stream
+            // was successfully terminated if we have some successfully decoded frames
+            // https://dxr.mozilla.org/firefox/source/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp#894-909
+            catch (UnknownBlockTypeException) when (frames.Count > 0)
+            {
+                break;
+            }
         }
+
+        Frames = frames.AsReadOnly();
+        Extensions = specialExtensions.AsReadOnly();
     }
-}
+}

+ 16 - 11
src/XamlAnimatedGif/Decoding/GifDecoderException.cs

@@ -1,17 +1,22 @@
-using System;
 using System.Runtime.Serialization;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+[Serializable]
+public abstract class GifDecoderException : Exception
 {
-    [Serializable]
-    public abstract class GifDecoderException : Exception
+    protected GifDecoderException(string message) : base(message)
+    {
+    }
+
+    protected GifDecoderException(string message, Exception inner) : base(message, inner)
     {
-        protected GifDecoderException(string message) : base(message) { }
-        protected GifDecoderException(string message, Exception inner) : base(message, inner) { }
+    }
 
-        protected GifDecoderException(
-          SerializationInfo info,
-          StreamingContext context)
-            : base(info, context) { }
+    protected GifDecoderException(
+        SerializationInfo info,
+        StreamingContext context)
+        : base(info, context)
+    {
     }
-}
+}

+ 22 - 21
src/XamlAnimatedGif/Decoding/GifExtension.cs

@@ -1,28 +1,29 @@
-using System.Collections.Generic;
 using System.IO;
-using System.Threading.Tasks;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal abstract class GifExtension : GifBlock
 {
-    internal abstract class GifExtension : GifBlock
+    internal const int ExtensionIntroducer = 0x21;
+
+    internal new static async Task<GifExtension> ReadAsync(Stream stream,
+        IEnumerable<GifExtension> controlExtensions)
     {
-        internal const int ExtensionIntroducer = 0x21;
+        // Note: at this point, the Extension Introducer (0x21) has already been read
 
-        internal new static async Task<GifExtension> ReadAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
+        var label = stream.ReadByte();
+        if (label < 0)
+            throw new EndOfStreamException();
+        return label switch
         {
-            // Note: at this point, the Extension Introducer (0x21) has already been read
-
-            int label = stream.ReadByte();
-            if (label < 0)
-                throw new EndOfStreamException();
-            return label switch
-            {
-                GifGraphicControlExtension.ExtensionLabel => await GifGraphicControlExtension.ReadAsync(stream).ConfigureAwait(false),
-                GifCommentExtension.ExtensionLabel => await GifCommentExtension.ReadAsync(stream).ConfigureAwait(false),
-                GifPlainTextExtension.ExtensionLabel => await GifPlainTextExtension.ReadAsync(stream, controlExtensions).ConfigureAwait(false),
-                GifApplicationExtension.ExtensionLabel => await GifApplicationExtension.ReadAsync(stream).ConfigureAwait(false),
-                _ => throw GifHelpers.UnknownExtensionTypeException(label),
-            };
-        }
+            GifGraphicControlExtension.ExtensionLabel => await GifGraphicControlExtension.ReadAsync(stream)
+                .ConfigureAwait(false),
+            GifCommentExtension.ExtensionLabel => await GifCommentExtension.ReadAsync(stream).ConfigureAwait(false),
+            GifPlainTextExtension.ExtensionLabel => await GifPlainTextExtension.ReadAsync(stream, controlExtensions)
+                .ConfigureAwait(false),
+            GifApplicationExtension.ExtensionLabel => await GifApplicationExtension.ReadAsync(stream)
+                .ConfigureAwait(false),
+            _ => throw GifHelpers.UnknownExtensionTypeException(label),
+        };
     }
-}
+}

+ 32 - 37
src/XamlAnimatedGif/Decoding/GifFrame.cs

@@ -1,50 +1,45 @@
-using System.Collections.Generic;
 using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifFrame : GifBlock
 {
-    internal class GifFrame : GifBlock
-    {
-        internal const int ImageSeparator = 0x2C;
+    internal const int ImageSeparator = 0x2C;
 
-        public GifImageDescriptor Descriptor { get; private set; }
-        public GifColor[] LocalColorTable { get; private set; }
-        public IList<GifExtension> Extensions { get; private set; }
-        public GifImageData ImageData { get; private set; }
-        public GifGraphicControlExtension GraphicControl { get; set; }
+    public GifImageDescriptor Descriptor { get; private set; }
+    public GifColor[] LocalColorTable { get; private set; }
+    public IList<GifExtension> Extensions { get; private set; }
+    public GifImageData ImageData { get; private set; }
+    public GifGraphicControlExtension GraphicControl { get; set; }
 
-        private GifFrame()
-        {
-        }
+    private GifFrame()
+    {
+    }
 
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.GraphicRendering; }
-        }
+    internal override GifBlockKind Kind => GifBlockKind.GraphicRendering;
 
-        internal new static async Task<GifFrame> ReadAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
-        {
-            var frame = new GifFrame();
+    internal new static async Task<GifFrame> ReadAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
+    {
+        var frame = new GifFrame();
 
-            await frame.ReadInternalAsync(stream, controlExtensions).ConfigureAwait(false);
+        await frame.ReadInternalAsync(stream, controlExtensions).ConfigureAwait(false);
 
-            return frame;
-        }
+        return frame;
+    }
 
-        private async Task ReadInternalAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
+    private async Task ReadInternalAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
+    {
+        // Note: at this point, the Image Separator (0x2C) has already been read
+
+        Descriptor = await GifImageDescriptor.ReadAsync(stream).ConfigureAwait(false);
+        if (Descriptor.HasLocalColorTable)
         {
-            // Note: at this point, the Image Separator (0x2C) has already been read
-
-            Descriptor = await GifImageDescriptor.ReadAsync(stream).ConfigureAwait(false);
-            if (Descriptor.HasLocalColorTable)
-            {
-                LocalColorTable = await GifHelpers.ReadColorTableAsync(stream, Descriptor.LocalColorTableSize).ConfigureAwait(false);
-            }
-            ImageData = await GifImageData.ReadAsync(stream).ConfigureAwait(false);
-            Extensions = controlExtensions.ToList().AsReadOnly();
-            GraphicControl = Extensions.OfType<GifGraphicControlExtension>().LastOrDefault();
+            LocalColorTable = await GifHelpers.ReadColorTableAsync(stream, Descriptor.LocalColorTableSize)
+                .ConfigureAwait(false);
         }
+
+        ImageData = await GifImageData.ReadAsync(stream).ConfigureAwait(false);
+        Extensions = controlExtensions.ToList().AsReadOnly();
+        GraphicControl = Extensions.OfType<GifGraphicControlExtension>().LastOrDefault();
     }
-}
+}

+ 8 - 9
src/XamlAnimatedGif/Decoding/GifFrameDisposalMethod.cs

@@ -1,10 +1,9 @@
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+public enum GifFrameDisposalMethod
 {
-    public enum GifFrameDisposalMethod
-    {
-        None = 0,
-        DoNotDispose = 1,
-        RestoreBackground = 2,
-        RestorePrevious = 3
-    }
-}
+    None = 0,
+    DoNotDispose = 1,
+    RestoreBackground = 2,
+    RestorePrevious = 3
+}

+ 41 - 48
src/XamlAnimatedGif/Decoding/GifGraphicControlExtension.cs

@@ -1,54 +1,47 @@
-using System;
 using System.IO;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+// label 0xF9
+internal class GifGraphicControlExtension : GifExtension
 {
-    // label 0xF9
-    internal class GifGraphicControlExtension : GifExtension
+    internal const int ExtensionLabel = 0xF9;
+
+    public int BlockSize { get; private set; }
+    public GifFrameDisposalMethod DisposalMethod { get; private set; }
+    public bool UserInput { get; private set; }
+    public bool HasTransparency { get; private set; }
+    public int Delay { get; private set; }
+    public int TransparencyIndex { get; private set; }
+
+    private GifGraphicControlExtension()
+    {
+    }
+
+    internal override GifBlockKind Kind => GifBlockKind.Control;
+
+    internal static async Task<GifGraphicControlExtension> ReadAsync(Stream stream)
+    {
+        var ext = new GifGraphicControlExtension();
+        await ext.ReadInternalAsync(stream).ConfigureAwait(false);
+        return ext;
+    }
+
+    private async Task ReadInternalAsync(Stream stream)
     {
-        internal const int ExtensionLabel = 0xF9;
-
-        public int BlockSize { get; private set; }
-        public GifFrameDisposalMethod DisposalMethod { get; private set; }
-        public bool UserInput { get; private set; }
-        public bool HasTransparency { get; private set; }
-        public int Delay { get; private set; }
-        public int TransparencyIndex { get; private set; }
-
-        private GifGraphicControlExtension()
-        {
-
-        }
-
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.Control; }
-        }
-
-        internal static async Task<GifGraphicControlExtension> ReadAsync(Stream stream)
-        {
-            var ext = new GifGraphicControlExtension();
-            await ext.ReadInternalAsync(stream).ConfigureAwait(false);
-            return ext;
-        }
-
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            // Note: at this point, the label (0xF9) has already been read
-
-            byte[] bytes = new byte[6];
-            await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-            BlockSize = bytes[0]; // should always be 4
-            if (BlockSize != 4)
-                throw GifHelpers.InvalidBlockSizeException("Graphic Control Extension", 4, BlockSize);
-            byte packedFields = bytes[1];
-            DisposalMethod = (GifFrameDisposalMethod) ((packedFields & 0x1C) >> 2);
-            UserInput = (packedFields & 0x02) != 0;
-            HasTransparency = (packedFields & 0x01) != 0;
-            Delay = BitConverter.ToUInt16(bytes, 2) * 10; // milliseconds
-            TransparencyIndex = bytes[4];
-        }
+        // Note: at this point, the label (0xF9) has already been read
+
+        var bytes = new byte[6];
+        await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+        BlockSize = bytes[0]; // should always be 4
+        if (BlockSize != 4)
+            throw GifHelpers.InvalidBlockSizeException("Graphic Control Extension", 4, BlockSize);
+        var packedFields = bytes[1];
+        DisposalMethod = (GifFrameDisposalMethod)((packedFields & 0x1C) >> 2);
+        UserInput = (packedFields & 0x02) != 0;
+        HasTransparency = (packedFields & 0x01) != 0;
+        Delay = BitConverter.ToUInt16(bytes, 2) * 10; // milliseconds
+        TransparencyIndex = bytes[4];
     }
-}
+}

+ 26 - 31
src/XamlAnimatedGif/Decoding/GifHeader.cs

@@ -1,39 +1,34 @@
 using System.IO;
-using System.Threading.Tasks;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifHeader : GifBlock
 {
-    internal class GifHeader : GifBlock
-    {
-        public string Signature { get; private set; }
-        public string Version { get; private set; }
-        public GifLogicalScreenDescriptor LogicalScreenDescriptor { get; private set; }
+    public string Signature { get; private set; }
+    public string Version { get; private set; }
+    public GifLogicalScreenDescriptor LogicalScreenDescriptor { get; private set; }
 
-        private GifHeader()
-        {
-        }
+    private GifHeader()
+    {
+    }
 
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.Other; }
-        }
+    internal override GifBlockKind Kind => GifBlockKind.Other;
 
-        internal static async Task<GifHeader> ReadAsync(Stream stream)
-        {
-            var header = new GifHeader();
-            await header.ReadInternalAsync(stream).ConfigureAwait(false);
-            return header;
-        }
+    internal static async Task<GifHeader> ReadAsync(Stream stream)
+    {
+        var header = new GifHeader();
+        await header.ReadInternalAsync(stream).ConfigureAwait(false);
+        return header;
+    }
 
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            Signature = await GifHelpers.ReadStringAsync(stream, 3).ConfigureAwait(false);
-            if (Signature != "GIF")
-                throw GifHelpers.InvalidSignatureException(Signature);
-            Version = await GifHelpers.ReadStringAsync(stream, 3).ConfigureAwait(false);
-            if (Version != "87a" && Version != "89a")
-                throw GifHelpers.UnsupportedVersionException(Version);
-            LogicalScreenDescriptor = await GifLogicalScreenDescriptor.ReadAsync(stream).ConfigureAwait(false);
-        }
+    private async Task ReadInternalAsync(Stream stream)
+    {
+        Signature = await GifHelpers.ReadStringAsync(stream, 3).ConfigureAwait(false);
+        if (Signature != "GIF")
+            throw GifHelpers.InvalidSignatureException(Signature);
+        Version = await GifHelpers.ReadStringAsync(stream, 3).ConfigureAwait(false);
+        if (Version != "87a" && Version != "89a")
+            throw GifHelpers.UnsupportedVersionException(Version);
+        LogicalScreenDescriptor = await GifLogicalScreenDescriptor.ReadAsync(stream).ConfigureAwait(false);
     }
-}
+}

+ 85 - 84
src/XamlAnimatedGif/Decoding/GifHelpers.cs

@@ -1,114 +1,115 @@
-using System;
 using System.IO;
 using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal static class GifHelpers
 {
-    internal static class GifHelpers
+    public static async Task<string> ReadStringAsync(Stream stream, int length)
     {
-        public static async Task<string> ReadStringAsync(Stream stream, int length)
-        {
-            byte[] bytes = new byte[length];
-            await stream.ReadAllAsync(bytes, 0, length).ConfigureAwait(false);
-            return GetString(bytes);
-        }
+        var bytes = new byte[length];
+        await stream.ReadAllAsync(bytes, 0, length).ConfigureAwait(false);
+        return GetString(bytes);
+    }
 
-        public static async Task ConsumeDataBlocksAsync(Stream sourceStream, CancellationToken cancellationToken = default)
-        {
-            await CopyDataBlocksToStreamAsync(sourceStream, Stream.Null, cancellationToken);
-        }
+    public static async Task ConsumeDataBlocksAsync(Stream sourceStream,
+        CancellationToken cancellationToken = default)
+    {
+        await CopyDataBlocksToStreamAsync(sourceStream, Stream.Null, cancellationToken);
+    }
 
-        public static async Task<byte[]> ReadDataBlocksAsync(Stream stream, CancellationToken cancellationToken = default)
-        {
-            using var ms = new MemoryStream();
-            await CopyDataBlocksToStreamAsync(stream, ms, cancellationToken);
-            return ms.ToArray();
-        }
+    public static async Task<byte[]> ReadDataBlocksAsync(Stream stream,
+        CancellationToken cancellationToken = default)
+    {
+        using var ms = new MemoryStream();
+        await CopyDataBlocksToStreamAsync(stream, ms, cancellationToken);
+        return ms.ToArray();
+    }
 
-        public static async Task CopyDataBlocksToStreamAsync(Stream sourceStream, Stream targetStream, CancellationToken cancellationToken = default)
+    public static async Task CopyDataBlocksToStreamAsync(Stream sourceStream, Stream targetStream,
+        CancellationToken cancellationToken = default)
+    {
+        int len;
+        // the length is on 1 byte, so each data sub-block can't be more than 255 bytes long
+        var buffer = new byte[255];
+        while ((len = await sourceStream.ReadByteAsync(cancellationToken)) > 0)
         {
-            int len;
-            // the length is on 1 byte, so each data sub-block can't be more than 255 bytes long
-            byte[] buffer = new byte[255];
-            while ((len = await sourceStream.ReadByteAsync(cancellationToken)) > 0)
-            {
-                await sourceStream.ReadAllAsync(buffer, 0, len, cancellationToken).ConfigureAwait(false);
+            await sourceStream.ReadAllAsync(buffer, 0, len, cancellationToken).ConfigureAwait(false);
 #if LACKS_STREAM_MEMORY_OVERLOADS
                 await targetStream.WriteAsync(buffer, 0, len, cancellationToken);
 #else
-                await targetStream.WriteAsync(buffer.AsMemory(0, len), cancellationToken);
+            await targetStream.WriteAsync(buffer.AsMemory(0, len), cancellationToken);
 #endif
-            }
         }
+    }
 
-        public static async Task<GifColor[]> ReadColorTableAsync(Stream stream, int size)
+    public static async Task<GifColor[]> ReadColorTableAsync(Stream stream, int size)
+    {
+        var length = 3 * size;
+        var bytes = new byte[length];
+        await stream.ReadAllAsync(bytes, 0, length).ConfigureAwait(false);
+        var colorTable = new GifColor[size];
+        for (var i = 0; i < size; i++)
         {
-            int length = 3 * size;
-            byte[] bytes = new byte[length];
-            await stream.ReadAllAsync(bytes, 0, length).ConfigureAwait(false);
-            GifColor[] colorTable = new GifColor[size];
-            for (int i = 0; i < size; i++)
-            {
-                byte r = bytes[3 * i];
-                byte g = bytes[3 * i + 1];
-                byte b = bytes[3 * i + 2];
-                colorTable[i] = new GifColor(r, g, b);
-            }
-            return colorTable;
+            var r = bytes[3 * i];
+            var g = bytes[3 * i + 1];
+            var b = bytes[3 * i + 2];
+            colorTable[i] = new GifColor(r, g, b);
         }
 
-        public static bool IsNetscapeExtension(GifApplicationExtension ext)
-        {
-            return ext.ApplicationIdentifier == "NETSCAPE"
-                && GetString(ext.AuthenticationCode) == "2.0";
-        }
+        return colorTable;
+    }
 
-        public static ushort GetRepeatCount(GifApplicationExtension ext)
-        {
-            if (ext.Data.Length >= 3)
-            {
-                return BitConverter.ToUInt16(ext.Data, 1);
-            }
-            return 1;
-        }
+    public static bool IsNetscapeExtension(GifApplicationExtension ext)
+    {
+        return ext.ApplicationIdentifier == "NETSCAPE"
+               && GetString(ext.AuthenticationCode) == "2.0";
+    }
 
-        public static Exception UnknownBlockTypeException(int blockId)
+    public static ushort GetRepeatCount(GifApplicationExtension ext)
+    {
+        if (ext.Data.Length >= 3)
         {
-            return new UnknownBlockTypeException("Unknown block type: 0x" + blockId.ToString("x2"));
+            return BitConverter.ToUInt16(ext.Data, 1);
         }
 
-        public static Exception UnknownExtensionTypeException(int extensionLabel)
-        {
-            return new UnknownExtensionTypeException("Unknown extension type: 0x" + extensionLabel.ToString("x2"));
-        }
+        return 1;
+    }
 
-        public static Exception InvalidBlockSizeException(string blockName, int expectedBlockSize, int actualBlockSize)
-        {
-            return new InvalidBlockSizeException(
-                $"Invalid block size for {blockName}. Expected {expectedBlockSize}, but was {actualBlockSize}");
-        }
+    public static Exception UnknownBlockTypeException(int blockId)
+    {
+        return new UnknownBlockTypeException("Unknown block type: 0x" + blockId.ToString("x2"));
+    }
 
-        public static Exception InvalidSignatureException(string signature)
-        {
-            return new InvalidSignatureException("Invalid file signature: " + signature);
-        }
+    public static Exception UnknownExtensionTypeException(int extensionLabel)
+    {
+        return new UnknownExtensionTypeException("Unknown extension type: 0x" + extensionLabel.ToString("x2"));
+    }
 
-        public static Exception UnsupportedVersionException(string version)
-        {
-            return new UnsupportedGifVersionException("Unsupported version: " + version);
-        }
+    public static Exception InvalidBlockSizeException(string blockName, int expectedBlockSize, int actualBlockSize)
+    {
+        return new InvalidBlockSizeException(
+            $"Invalid block size for {blockName}. Expected {expectedBlockSize}, but was {actualBlockSize}");
+    }
 
-        public static string GetString(byte[] bytes)
-        {
-            return GetString(bytes, 0, bytes.Length);
-        }
+    public static Exception InvalidSignatureException(string signature)
+    {
+        return new InvalidSignatureException("Invalid file signature: " + signature);
+    }
 
-        public static string GetString(byte[] bytes, int index, int count)
-        {
-            return Encoding.UTF8.GetString(bytes, index, count);
-        }
+    public static Exception UnsupportedVersionException(string version)
+    {
+        return new UnsupportedGifVersionException("Unsupported version: " + version);
+    }
+
+    public static string GetString(byte[] bytes)
+    {
+        return GetString(bytes, 0, bytes.Length);
+    }
+
+    public static string GetString(byte[] bytes, int index, int count)
+    {
+        return Encoding.UTF8.GetString(bytes, index, count);
     }
-}
+}

+ 20 - 22
src/XamlAnimatedGif/Decoding/GifImageData.cs

@@ -1,29 +1,27 @@
 using System.IO;
-using System.Threading.Tasks;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifImageData
 {
-    internal class GifImageData
-    {
-        public byte LzwMinimumCodeSize { get; set; }
-        public long CompressedDataStartOffset { get; set; }
+    public byte LzwMinimumCodeSize { get; set; }
+    public long CompressedDataStartOffset { get; set; }
 
-        private GifImageData()
-        {
-        }
+    private GifImageData()
+    {
+    }
 
-        internal static async Task<GifImageData> ReadAsync(Stream stream)
-        {
-            var imgData = new GifImageData();
-            await imgData.ReadInternalAsync(stream).ConfigureAwait(false);
-            return imgData;
-        }
+    internal static async Task<GifImageData> ReadAsync(Stream stream)
+    {
+        var imgData = new GifImageData();
+        await imgData.ReadInternalAsync(stream).ConfigureAwait(false);
+        return imgData;
+    }
 
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            LzwMinimumCodeSize = (byte)stream.ReadByte();
-            CompressedDataStartOffset = stream.Position;
-            await GifHelpers.ConsumeDataBlocksAsync(stream).ConfigureAwait(false);
-        }
+    private async Task ReadInternalAsync(Stream stream)
+    {
+        LzwMinimumCodeSize = (byte)stream.ReadByte();
+        CompressedDataStartOffset = stream.Position;
+        await GifHelpers.ConsumeDataBlocksAsync(stream).ConfigureAwait(false);
     }
-}
+}

+ 34 - 37
src/XamlAnimatedGif/Decoding/GifImageDescriptor.cs

@@ -1,45 +1,42 @@
-using System;
 using System.IO;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifImageDescriptor : IGifRect
 {
-    internal class GifImageDescriptor : IGifRect
-    {
-        public int Left { get; private set; }
-        public int Top { get; private set; }
-        public int Width { get; private set; }
-        public int Height { get; private set; }
-        public bool HasLocalColorTable { get; private set; }
-        public bool Interlace { get; private set; }
-        public bool IsLocalColorTableSorted { get; private set; }
-        public int LocalColorTableSize { get; private set; }
+    public int Left { get; private set; }
+    public int Top { get; private set; }
+    public int Width { get; private set; }
+    public int Height { get; private set; }
+    public bool HasLocalColorTable { get; private set; }
+    public bool Interlace { get; private set; }
+    public bool IsLocalColorTableSorted { get; private set; }
+    public int LocalColorTableSize { get; private set; }
 
-        private GifImageDescriptor()
-        {
-        }
+    private GifImageDescriptor()
+    {
+    }
 
-        internal static async Task<GifImageDescriptor> ReadAsync(Stream stream)
-        {
-            var descriptor = new GifImageDescriptor();
-            await descriptor.ReadInternalAsync(stream).ConfigureAwait(false);
-            return descriptor;
-        }
+    internal static async Task<GifImageDescriptor> ReadAsync(Stream stream)
+    {
+        var descriptor = new GifImageDescriptor();
+        await descriptor.ReadInternalAsync(stream).ConfigureAwait(false);
+        return descriptor;
+    }
 
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            byte[] bytes = new byte[9];
-            await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-            Left = BitConverter.ToUInt16(bytes, 0);
-            Top = BitConverter.ToUInt16(bytes, 2);
-            Width = BitConverter.ToUInt16(bytes, 4);
-            Height = BitConverter.ToUInt16(bytes, 6);
-            byte packedFields = bytes[8];
-            HasLocalColorTable = (packedFields & 0x80) != 0;
-            Interlace = (packedFields & 0x40) != 0;
-            IsLocalColorTableSorted = (packedFields & 0x20) != 0;
-            LocalColorTableSize = 1 << ((packedFields & 0x07) + 1);
-        }
+    private async Task ReadInternalAsync(Stream stream)
+    {
+        var bytes = new byte[9];
+        await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+        Left = BitConverter.ToUInt16(bytes, 0);
+        Top = BitConverter.ToUInt16(bytes, 2);
+        Width = BitConverter.ToUInt16(bytes, 4);
+        Height = BitConverter.ToUInt16(bytes, 6);
+        var packedFields = bytes[8];
+        HasLocalColorTable = (packedFields & 0x80) != 0;
+        Interlace = (packedFields & 0x40) != 0;
+        IsLocalColorTableSorted = (packedFields & 0x20) != 0;
+        LocalColorTableSize = 1 << ((packedFields & 0x07) + 1);
     }
-}
+}

+ 37 - 46
src/XamlAnimatedGif/Decoding/GifLogicalScreenDescriptor.cs

@@ -1,55 +1,46 @@
-using System;
 using System.IO;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal class GifLogicalScreenDescriptor : IGifRect
 {
-    internal class GifLogicalScreenDescriptor : IGifRect
-    {
-        public int Width { get; private set; }
-        public int Height { get; private set; }
-        public bool HasGlobalColorTable { get; private set; }
-        public int ColorResolution { get; private set; }
-        public bool IsGlobalColorTableSorted { get; private set; }
-        public int GlobalColorTableSize { get; private set; }
-        public int BackgroundColorIndex { get; private set; }
-        public double PixelAspectRatio { get; private set; }
+    public int Width { get; private set; }
+    public int Height { get; private set; }
+    public bool HasGlobalColorTable { get; private set; }
+    public int ColorResolution { get; private set; }
+    public bool IsGlobalColorTableSorted { get; private set; }
+    public int GlobalColorTableSize { get; private set; }
+    public int BackgroundColorIndex { get; private set; }
+    public double PixelAspectRatio { get; private set; }
 
-        internal static async Task<GifLogicalScreenDescriptor> ReadAsync(Stream stream)
-        {
-            var descriptor = new GifLogicalScreenDescriptor();
-            await descriptor.ReadInternalAsync(stream).ConfigureAwait(false);
-            return descriptor;
-        }
+    internal static async Task<GifLogicalScreenDescriptor> ReadAsync(Stream stream)
+    {
+        var descriptor = new GifLogicalScreenDescriptor();
+        await descriptor.ReadInternalAsync(stream).ConfigureAwait(false);
+        return descriptor;
+    }
 
-        private async Task ReadInternalAsync(Stream stream)
-        {
-            byte[] bytes = new byte[7];
-            await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+    private async Task ReadInternalAsync(Stream stream)
+    {
+        var bytes = new byte[7];
+        await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
 
-            Width = BitConverter.ToUInt16(bytes, 0);
-            Height = BitConverter.ToUInt16(bytes, 2);
-            byte packedFields = bytes[4];
-            HasGlobalColorTable = (packedFields & 0x80) != 0;
-            ColorResolution = ((packedFields & 0x70) >> 4) + 1;
-            IsGlobalColorTableSorted = (packedFields & 0x08) != 0;
-            GlobalColorTableSize = 1 << ((packedFields & 0x07) + 1);
-            BackgroundColorIndex = bytes[5];
-            PixelAspectRatio =
-                bytes[6] == 0
-                    ? 0.0
-                    : (15 + bytes[6]) / 64.0;
-        }
+        Width = BitConverter.ToUInt16(bytes, 0);
+        Height = BitConverter.ToUInt16(bytes, 2);
+        var packedFields = bytes[4];
+        HasGlobalColorTable = (packedFields & 0x80) != 0;
+        ColorResolution = ((packedFields & 0x70) >> 4) + 1;
+        IsGlobalColorTableSorted = (packedFields & 0x08) != 0;
+        GlobalColorTableSize = 1 << ((packedFields & 0x07) + 1);
+        BackgroundColorIndex = bytes[5];
+        PixelAspectRatio =
+            bytes[6] == 0
+                ? 0.0
+                : (15 + bytes[6]) / 64.0;
+    }
 
-        int IGifRect.Left
-        {
-            get { return 0; }
-        }
+    int IGifRect.Left => 0;
 
-        int IGifRect.Top
-        {
-            get { return 0; }
-        }
-    }
-}
+    int IGifRect.Top => 0;
+}

+ 53 - 60
src/XamlAnimatedGif/Decoding/GifPlainTextExtension.cs

@@ -1,69 +1,62 @@
-using System;
-using System.Collections.Generic;
 using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+// label 0x01
+internal class GifPlainTextExtension : GifExtension
 {
-    // label 0x01
-    internal class GifPlainTextExtension : GifExtension
+    internal const int ExtensionLabel = 0x01;
+
+    public int BlockSize { get; private set; }
+    public int Left { get; private set; }
+    public int Top { get; private set; }
+    public int Width { get; private set; }
+    public int Height { get; private set; }
+    public int CellWidth { get; private set; }
+    public int CellHeight { get; private set; }
+    public int ForegroundColorIndex { get; private set; }
+    public int BackgroundColorIndex { get; private set; }
+    public string Text { get; private set; }
+
+    public IList<GifExtension> Extensions { get; private set; }
+
+    private GifPlainTextExtension()
     {
-        internal const int ExtensionLabel = 0x01;
-
-        public int BlockSize { get; private set; }
-        public int Left { get; private set; }
-        public int Top { get; private set; }
-        public int Width { get; private set; }
-        public int Height { get; private set; }
-        public int CellWidth { get; private set; }
-        public int CellHeight { get; private set; }
-        public int ForegroundColorIndex { get; private set; }
-        public int BackgroundColorIndex { get; private set; }
-        public string Text { get; private set; }
-
-        public IList<GifExtension> Extensions { get; private set; }
-
-        private GifPlainTextExtension()
-        {
-        }
-
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.GraphicRendering; }
-        }
-
-        internal new static async Task<GifPlainTextExtension> ReadAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
-        {
-            var plainText = new GifPlainTextExtension();
-            await plainText.ReadInternalAsync(stream, controlExtensions).ConfigureAwait(false);
-            return plainText;
-        }
-
-        private async Task ReadInternalAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
-        {
-            // Note: at this point, the label (0x01) has already been read
-
-            byte[] bytes = new byte[13];
-            await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+    }
 
-            BlockSize = bytes[0];
-            if (BlockSize != 12)
-                throw GifHelpers.InvalidBlockSizeException("Plain Text Extension", 12, BlockSize);
+    internal override GifBlockKind Kind => GifBlockKind.GraphicRendering;
 
-            Left = BitConverter.ToUInt16(bytes, 1);
-            Top = BitConverter.ToUInt16(bytes, 3);
-            Width = BitConverter.ToUInt16(bytes, 5);
-            Height = BitConverter.ToUInt16(bytes, 7);
-            CellWidth = bytes[9];
-            CellHeight = bytes[10];
-            ForegroundColorIndex = bytes[11];
-            BackgroundColorIndex = bytes[12];
+    internal new static async Task<GifPlainTextExtension> ReadAsync(Stream stream,
+        IEnumerable<GifExtension> controlExtensions)
+    {
+        var plainText = new GifPlainTextExtension();
+        await plainText.ReadInternalAsync(stream, controlExtensions).ConfigureAwait(false);
+        return plainText;
+    }
 
-            var dataBytes = await GifHelpers.ReadDataBlocksAsync(stream).ConfigureAwait(false);
-            Text = GifHelpers.GetString(dataBytes);
-            Extensions = controlExtensions.ToList().AsReadOnly();
-        }
+    private async Task ReadInternalAsync(Stream stream, IEnumerable<GifExtension> controlExtensions)
+    {
+        // Note: at this point, the label (0x01) has already been read
+
+        var bytes = new byte[13];
+        await stream.ReadAllAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+
+        BlockSize = bytes[0];
+        if (BlockSize != 12)
+            throw GifHelpers.InvalidBlockSizeException("Plain Text Extension", 12, BlockSize);
+
+        Left = BitConverter.ToUInt16(bytes, 1);
+        Top = BitConverter.ToUInt16(bytes, 3);
+        Width = BitConverter.ToUInt16(bytes, 5);
+        Height = BitConverter.ToUInt16(bytes, 7);
+        CellWidth = bytes[9];
+        CellHeight = bytes[10];
+        ForegroundColorIndex = bytes[11];
+        BackgroundColorIndex = bytes[12];
+
+        var dataBytes = await GifHelpers.ReadDataBlocksAsync(stream).ConfigureAwait(false);
+        Text = GifHelpers.GetString(dataBytes);
+        Extensions = controlExtensions.ToList().AsReadOnly();
     }
-}
+}

+ 11 - 17
src/XamlAnimatedGif/Decoding/GifTrailer.cs

@@ -1,23 +1,17 @@
-using System.Threading.Tasks;
+namespace XamlAnimatedGif.Decoding;
 
-namespace XamlAnimatedGif.Decoding
+internal class GifTrailer : GifBlock
 {
-    internal class GifTrailer : GifBlock
-    {
-        internal const int TrailerByte = 0x3B;
+    internal const int TrailerByte = 0x3B;
 
-        private GifTrailer()
-        {
-        }
+    private GifTrailer()
+    {
+    }
 
-        internal override GifBlockKind Kind
-        {
-            get { return GifBlockKind.Other; }
-        }
+    internal override GifBlockKind Kind => GifBlockKind.Other;
 
-        internal static Task<GifTrailer> ReadAsync()
-        {
-            return Task.FromResult(new GifTrailer());
-        }
+    internal static Task<GifTrailer> ReadAsync()
+    {
+        return Task.FromResult(new GifTrailer());
     }
-}
+}

+ 7 - 8
src/XamlAnimatedGif/Decoding/IGifRect.cs

@@ -1,10 +1,9 @@
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+internal interface IGifRect
 {
-    internal interface IGifRect
-    {
-        int Left { get; }
-        int Top { get; }
-        int Width { get; }
-        int Height { get; }
-    }
+    int Left { get; }
+    int Top { get; }
+    int Width { get; }
+    int Height { get; }
 }

+ 15 - 10
src/XamlAnimatedGif/Decoding/InvalidBlockSizeException.cs

@@ -1,17 +1,22 @@
-using System;
 using System.Runtime.Serialization;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+[Serializable]
+public class InvalidBlockSizeException : GifDecoderException
 {
-    [Serializable]
-    public class InvalidBlockSizeException : GifDecoderException
+    internal InvalidBlockSizeException(string message) : base(message)
+    {
+    }
+
+    internal InvalidBlockSizeException(string message, Exception inner) : base(message, inner)
     {
-        internal InvalidBlockSizeException(string message) : base(message) { }
-        internal InvalidBlockSizeException(string message, Exception inner) : base(message, inner) { }
+    }
 
-        protected InvalidBlockSizeException(
-            SerializationInfo info,
-            StreamingContext context)
-            : base(info, context) { }
+    protected InvalidBlockSizeException(
+        SerializationInfo info,
+        StreamingContext context)
+        : base(info, context)
+    {
     }
 }

+ 15 - 10
src/XamlAnimatedGif/Decoding/InvalidSignatureException.cs

@@ -1,18 +1,23 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+[Serializable]
+public class InvalidSignatureException : GifDecoderException
 {
-    [Serializable]
-    public class InvalidSignatureException : GifDecoderException
+    internal InvalidSignatureException(string message) : base(message)
+    {
+    }
+
+    internal InvalidSignatureException(string message, Exception inner) : base(message, inner)
     {
-        internal InvalidSignatureException(string message) : base(message) { }
-        internal InvalidSignatureException(string message, Exception inner) : base(message, inner) { }
+    }
 
-        protected InvalidSignatureException(
-            SerializationInfo info,
-            StreamingContext context)
-            : base(info, context)
-        { }
+    protected InvalidSignatureException(
+        SerializationInfo info,
+        StreamingContext context)
+        : base(info, context)
+    {
     }
 }

+ 16 - 12
src/XamlAnimatedGif/Decoding/UnknownBlockTypeException.cs

@@ -1,18 +1,22 @@
-using System;
-using System.Runtime.Serialization;
+using System.Runtime.Serialization;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+[Serializable]
+public class UnknownBlockTypeException : GifDecoderException
 {
-    [Serializable]
-    public class UnknownBlockTypeException : GifDecoderException
+    internal UnknownBlockTypeException(string message) : base(message)
+    {
+    }
+
+    internal UnknownBlockTypeException(string message, Exception inner) : base(message, inner)
     {
-        internal UnknownBlockTypeException(string message) : base(message) { }
-        internal UnknownBlockTypeException(string message, Exception inner) : base(message, inner) { }
+    }
 
-        protected UnknownBlockTypeException(
-            SerializationInfo info,
-            StreamingContext context)
-            : base(info, context)
-        { }
+    protected UnknownBlockTypeException(
+        SerializationInfo info,
+        StreamingContext context)
+        : base(info, context)
+    {
     }
 }

+ 16 - 12
src/XamlAnimatedGif/Decoding/UnknownExtensionTypeException.cs

@@ -1,18 +1,22 @@
-using System;
-using System.Runtime.Serialization;
+using System.Runtime.Serialization;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+[Serializable]
+public class UnknownExtensionTypeException : GifDecoderException
 {
-    [Serializable]
-    public class UnknownExtensionTypeException : GifDecoderException
+    internal UnknownExtensionTypeException(string message) : base(message)
+    {
+    }
+
+    internal UnknownExtensionTypeException(string message, Exception inner) : base(message, inner)
     {
-        internal UnknownExtensionTypeException(string message) : base(message) { }
-        internal UnknownExtensionTypeException(string message, Exception inner) : base(message, inner) { }
+    }
 
-        protected UnknownExtensionTypeException(
-            SerializationInfo info,
-            StreamingContext context)
-            : base(info, context)
-        { }
+    protected UnknownExtensionTypeException(
+        SerializationInfo info,
+        StreamingContext context)
+        : base(info, context)
+    {
     }
 }

+ 16 - 12
src/XamlAnimatedGif/Decoding/UnsupportedGifVersionException.cs

@@ -1,18 +1,22 @@
-using System;
-using System.Runtime.Serialization;
+using System.Runtime.Serialization;
 
-namespace XamlAnimatedGif.Decoding
+namespace XamlAnimatedGif.Decoding;
+
+[Serializable]
+public class UnsupportedGifVersionException : GifDecoderException
 {
-    [Serializable]
-    public class UnsupportedGifVersionException : GifDecoderException
+    internal UnsupportedGifVersionException(string message) : base(message)
+    {
+    }
+
+    internal UnsupportedGifVersionException(string message, Exception inner) : base(message, inner)
     {
-        internal UnsupportedGifVersionException(string message) : base(message) { }
-        internal UnsupportedGifVersionException(string message, Exception inner) : base(message, inner) { }
+    }
 
-        protected UnsupportedGifVersionException(
-            SerializationInfo info,
-            StreamingContext context)
-            : base(info, context)
-        { }
+    protected UnsupportedGifVersionException(
+        SerializationInfo info,
+        StreamingContext context)
+        : base(info, context)
+    {
     }
 }

+ 47 - 45
src/XamlAnimatedGif/Decompression/BitReader.cs

@@ -1,56 +1,58 @@
-namespace XamlAnimatedGif.Decompression
+namespace XamlAnimatedGif.Decompression;
+
+internal class BitReader
 {
-    class BitReader
+    private readonly byte[] _buffer;
+
+    public BitReader(byte[] buffer)
+    {
+        _buffer = buffer;
+    }
+
+    private int _bytePosition = -1;
+    private int _bitPosition;
+    private int _currentValue = -1;
+
+    public int ReadBits(int bitCount)
     {
-        private readonly byte[] _buffer;
+        // The following code assumes it's running on a little-endian architecture.
+        // It's probably safe to assume it will always be the case, because:
+        // - Windows only supports little-endian architectures: x86/x64 and ARM (which supports
+        //   both endiannesses, but Windows on ARM is always in little-endian mode)
+        // - No platforms other than Windows support XAML applications
+        // If the situation changes, this code will have to be updated.
 
-        public BitReader(byte[] buffer)
+        if (_bytePosition == -1)
         {
-            _buffer = buffer;
+            _bytePosition = 0;
+            _bitPosition = 0;
+            _currentValue = ReadInt32(); //BitConverter.ToInt32(_buffer, _bytePosition);
         }
-
-        private int _bytePosition = -1;
-        private int _bitPosition;
-        private int _currentValue = -1;
-        public int ReadBits(int bitCount)
+        else if (bitCount > 32 - _bitPosition)
         {
-            // The following code assumes it's running on a little-endian architecture.
-            // It's probably safe to assume it will always be the case, because:
-            // - Windows only supports little-endian architectures: x86/x64 and ARM (which supports
-            //   both endiannesses, but Windows on ARM is always in little-endian mode)
-            // - No platforms other than Windows support XAML applications
-            // If the situation changes, this code will have to be updated.
-
-            if (_bytePosition == -1)
-            {
-                _bytePosition = 0;
-                _bitPosition = 0;
-                _currentValue = ReadInt32(); //BitConverter.ToInt32(_buffer, _bytePosition);
-            }
-            else if (bitCount > 32 - _bitPosition)
-            {
-                int n = _bitPosition >> 3;
-                _bytePosition += n;
-                _bitPosition &= 0x07;
-                _currentValue = ReadInt32() >> _bitPosition;
-            }
-            int mask = (1 << bitCount) - 1;
-            int value = _currentValue & mask;
-            _currentValue >>= bitCount;
-            _bitPosition += bitCount;
-            return value;
+            var n = _bitPosition >> 3;
+            _bytePosition += n;
+            _bitPosition &= 0x07;
+            _currentValue = ReadInt32() >> _bitPosition;
         }
 
-        private int ReadInt32()
+        var mask = (1 << bitCount) - 1;
+        var value = _currentValue & mask;
+        _currentValue >>= bitCount;
+        _bitPosition += bitCount;
+        return value;
+    }
+
+    private int ReadInt32()
+    {
+        var value = 0;
+        for (var i = 0; i < 4; i++)
         {
-            int value = 0;
-            for (int i = 0; i < 4; i++)
-            {
-                if (_bytePosition + i >= _buffer.Length)
-                    break;
-                value |= _buffer[_bytePosition + i] << (i << 3);
-            }
-            return value;
+            if (_bytePosition + i >= _buffer.Length)
+                break;
+            value |= _buffer[_bytePosition + i] << (i << 3);
         }
+
+        return value;
     }
-}
+}

+ 194 - 194
src/XamlAnimatedGif/Decompression/LzwDecompressStream.cs

@@ -1,251 +1,251 @@
-using System;
+using System.Diagnostics;
 using System.IO;
-using Buffer = System.Buffer;
 using System.Runtime.CompilerServices;
-using System.Diagnostics;
+using Buffer = System.Buffer;
 
-namespace XamlAnimatedGif.Decompression
+namespace XamlAnimatedGif.Decompression;
+
+internal class LzwDecompressStream : Stream
 {
-    class LzwDecompressStream : Stream
+    private const int MaxCodeLength = 12;
+    private readonly BitReader _reader;
+    private readonly CodeTable _codeTable;
+    private int _prevCode;
+    private byte[] _remainingBytes;
+    private bool _endOfStream;
+
+    public LzwDecompressStream(byte[] compressedBuffer, int minimumCodeLength)
     {
-        private const int MaxCodeLength = 12;
-        private readonly BitReader _reader;
-        private readonly CodeTable _codeTable;
-        private int _prevCode;
-        private byte[] _remainingBytes;
-        private bool _endOfStream;
-
-        public LzwDecompressStream(byte[] compressedBuffer, int minimumCodeLength)
-        {
-            _reader = new BitReader(compressedBuffer);
-            _codeTable = new CodeTable(minimumCodeLength);
-        }
-        public override void Flush()
-        {
-        }
+        _reader = new BitReader(compressedBuffer);
+        _codeTable = new CodeTable(minimumCodeLength);
+    }
 
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            throw new NotSupportedException();
-        }
+    public override void Flush()
+    {
+    }
 
-        public override void SetLength(long value)
-        {
-            throw new NotSupportedException();
-        }
+    public override long Seek(long offset, SeekOrigin origin)
+    {
+        throw new NotSupportedException();
+    }
 
-        public override int Read(byte[] buffer, int offset, int count)
-        {
-            ValidateReadArgs(buffer, offset, count);
+    public override void SetLength(long value)
+    {
+        throw new NotSupportedException();
+    }
 
-            if (_endOfStream)
-                return 0;
+    public override int Read(byte[] buffer, int offset, int count)
+    {
+        ValidateReadArgs(buffer, offset, count);
 
-            int read = 0;
+        if (_endOfStream)
+            return 0;
 
-            FlushRemainingBytes(buffer, offset, count, ref read);
+        var read = 0;
 
-            while (read < count)
+        FlushRemainingBytes(buffer, offset, count, ref read);
+
+        while (read < count)
+        {
+            var code = _reader.ReadBits(_codeTable.CodeLength);
+
+            if (!ProcessCode(code, buffer, offset, count, ref read))
             {
-                int code = _reader.ReadBits(_codeTable.CodeLength);
-                
-                if (!ProcessCode(code, buffer, offset, count, ref read))
-                {
-                    _endOfStream = true;
-                    break;
-                }
+                _endOfStream = true;
+                break;
             }
-            return read;
         }
 
-        public override void Write(byte[] buffer, int offset, int count)
-        {
-            throw new NotSupportedException();
-        }
+        return read;
+    }
 
-        public override bool CanRead => true;
+    public override void Write(byte[] buffer, int offset, int count)
+    {
+        throw new NotSupportedException();
+    }
 
-        public override bool CanSeek => false;
+    public override bool CanRead => true;
 
-        public override bool CanWrite => true;
+    public override bool CanSeek => false;
 
-        public override long Length
-        {
-            get { throw new NotSupportedException(); }
-        }
+    public override bool CanWrite => true;
 
-        public override long Position
-        {
-            get { throw new NotSupportedException(); }
-            set { throw new NotSupportedException(); }
-        }
+    public override long Length => throw new NotSupportedException();
 
-        private void InitCodeTable()
-        {
-            _codeTable.Reset();
-            _prevCode = -1;
-        }
+    public override long Position
+    {
+        get => throw new NotSupportedException();
+        set => throw new NotSupportedException();
+    }
 
-        private static byte[] CopySequenceToBuffer(byte[] sequence, byte[] buffer, int offset, int count, ref int read)
-        {
-            int bytesToRead = Math.Min(sequence.Length, count - read);
-            Buffer.BlockCopy(sequence, 0, buffer, offset + read, bytesToRead);
-            read += bytesToRead;
-            byte[] remainingBytes = null;
-            if (bytesToRead < sequence.Length)
-            {
-                int remainingBytesCount = sequence.Length - bytesToRead;
-                remainingBytes = new byte[remainingBytesCount];
-                Buffer.BlockCopy(sequence, bytesToRead, remainingBytes, 0, remainingBytesCount);
-            }
-            return remainingBytes;
-        }
+    private void InitCodeTable()
+    {
+        _codeTable.Reset();
+        _prevCode = -1;
+    }
 
-        private void FlushRemainingBytes(byte[] buffer, int offset, int count, ref int read)
+    private static byte[] CopySequenceToBuffer(byte[] sequence, byte[] buffer, int offset, int count, ref int read)
+    {
+        var bytesToRead = Math.Min(sequence.Length, count - read);
+        Buffer.BlockCopy(sequence, 0, buffer, offset + read, bytesToRead);
+        read += bytesToRead;
+        byte[] remainingBytes = null;
+        if (bytesToRead < sequence.Length)
         {
-            // If we read too many bytes last time, copy them first;
-            if (_remainingBytes != null)
-                _remainingBytes = CopySequenceToBuffer(_remainingBytes, buffer, offset, count, ref read);
+            var remainingBytesCount = sequence.Length - bytesToRead;
+            remainingBytes = new byte[remainingBytesCount];
+            Buffer.BlockCopy(sequence, bytesToRead, remainingBytes, 0, remainingBytesCount);
         }
 
-        [Conditional("DISABLED")]
-        private static void ValidateReadArgs(byte[] buffer, int offset, int count)
-        {
-            if (buffer == null) throw new ArgumentNullException(nameof(buffer));
-            if (offset < 0)
-                throw new ArgumentOutOfRangeException(nameof(offset), "Offset can't be negative");
-            if (count < 0)
-                throw new ArgumentOutOfRangeException(nameof(count), "Count can't be negative");
-            if (offset + count > buffer.Length)
-                throw new ArgumentException("Buffer is to small to receive the requested data");
-        }
+        return remainingBytes;
+    }
+
+    private void FlushRemainingBytes(byte[] buffer, int offset, int count, ref int read)
+    {
+        // If we read too many bytes last time, copy them first;
+        if (_remainingBytes != null)
+            _remainingBytes = CopySequenceToBuffer(_remainingBytes, buffer, offset, count, ref read);
+    }
+
+    [Conditional("DISABLED")]
+    private static void ValidateReadArgs(byte[] buffer, int offset, int count)
+    {
+        if (buffer == null) throw new ArgumentNullException(nameof(buffer));
+        if (offset < 0)
+            throw new ArgumentOutOfRangeException(nameof(offset), "Offset can't be negative");
+        if (count < 0)
+            throw new ArgumentOutOfRangeException(nameof(count), "Count can't be negative");
+        if (offset + count > buffer.Length)
+            throw new ArgumentException("Buffer is to small to receive the requested data");
+    }
 
-        private bool ProcessCode(int code, byte[] buffer, int offset, int count, ref int read)
+    private bool ProcessCode(int code, byte[] buffer, int offset, int count, ref int read)
+    {
+        if (code < _codeTable.Count)
         {
-            if (code < _codeTable.Count)
+            var sequence = _codeTable[code];
+            if (sequence.IsStopCode)
             {
-                var sequence = _codeTable[code];
-                if (sequence.IsStopCode)
-                {
-                    return false;
-                }
-                if (sequence.IsClearCode)
-                {
-                    InitCodeTable();
-                    return true;
-                }
-                _remainingBytes = CopySequenceToBuffer(sequence.Bytes, buffer, offset, count, ref read);
-                if (_prevCode >= 0)
-                {
-                    var prev = _codeTable[_prevCode];
-                    var newSequence = prev.Append(sequence.Bytes[0]);
-                    _codeTable.Add(newSequence);
-                }
+                return false;
             }
-            else
+
+            if (sequence.IsClearCode)
+            {
+                InitCodeTable();
+                return true;
+            }
+
+            _remainingBytes = CopySequenceToBuffer(sequence.Bytes, buffer, offset, count, ref read);
+            if (_prevCode >= 0)
             {
                 var prev = _codeTable[_prevCode];
-                var newSequence = prev.Append(prev.Bytes[0]);
+                var newSequence = prev.Append(sequence.Bytes[0]);
                 _codeTable.Add(newSequence);
-                _remainingBytes = CopySequenceToBuffer(newSequence.Bytes, buffer, offset, count, ref read);
             }
-            _prevCode = code;
-            return true;
+        }
+        else
+        {
+            var prev = _codeTable[_prevCode];
+            var newSequence = prev.Append(prev.Bytes[0]);
+            _codeTable.Add(newSequence);
+            _remainingBytes = CopySequenceToBuffer(newSequence.Bytes, buffer, offset, count, ref read);
         }
 
-        struct Sequence
+        _prevCode = code;
+        return true;
+    }
+
+    private struct Sequence
+    {
+        public Sequence(byte[] bytes)
+            : this()
         {
-            public Sequence(byte[] bytes)
-                : this()
-            {
-                Bytes = bytes;
-            }
+            Bytes = bytes;
+        }
 
-            private Sequence(bool isClearCode, bool isStopCode)
-                : this()
-            {
-                IsClearCode = isClearCode;
-                IsStopCode = isStopCode;
-            }
+        private Sequence(bool isClearCode, bool isStopCode)
+            : this()
+        {
+            IsClearCode = isClearCode;
+            IsStopCode = isStopCode;
+        }
 
-            public byte[] Bytes { get; }
+        public byte[] Bytes { get; }
 
-            public bool IsClearCode { get; }
+        public bool IsClearCode { get; }
 
-            public bool IsStopCode { get; }
+        public bool IsStopCode { get; }
 
-            public static Sequence ClearCode { get; } = new Sequence(true, false);
+        public static Sequence ClearCode { get; } = new(true, false);
 
-            public static Sequence StopCode { get; } = new Sequence(false, true);
+        public static Sequence StopCode { get; } = new(false, true);
 
-            public Sequence Append(byte b)
-            {
-                var bytes = new byte[Bytes.Length + 1];
-                Bytes.CopyTo(bytes, 0);
-                bytes[Bytes.Length] = b;
-                return new Sequence(bytes);
-            }
+        public Sequence Append(byte b)
+        {
+            var bytes = new byte[Bytes.Length + 1];
+            Bytes.CopyTo(bytes, 0);
+            bytes[Bytes.Length] = b;
+            return new Sequence(bytes);
         }
+    }
 
-        class CodeTable
-        {
-            private readonly int _minimumCodeLength;
-            private readonly Sequence[] _table;
-            private int _count;
-            private int _codeLength;
+    private class CodeTable
+    {
+        private readonly int _minimumCodeLength;
+        private readonly Sequence[] _table;
 
-            public CodeTable(int minimumCodeLength)
-            {
-                _minimumCodeLength = minimumCodeLength;
-                _codeLength = _minimumCodeLength + 1;
-                int initialEntries = 1 << minimumCodeLength;
-                _table = new Sequence[1 << MaxCodeLength];
-                for (int i = 0; i < initialEntries; i++)
-                {
-                    _table[_count++] = new Sequence(new[] {(byte) i});
-                }
-                Add(Sequence.ClearCode);
-                Add(Sequence.StopCode);
-            }
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            public void Reset()
+        public CodeTable(int minimumCodeLength)
+        {
+            _minimumCodeLength = minimumCodeLength;
+            CodeLength = _minimumCodeLength + 1;
+            var initialEntries = 1 << minimumCodeLength;
+            _table = new Sequence[1 << MaxCodeLength];
+            for (var i = 0; i < initialEntries; i++)
             {
-                _count = (1 << _minimumCodeLength) + 2;
-                _codeLength = _minimumCodeLength + 1;
+                _table[Count++] = new Sequence(new[] { (byte)i });
             }
 
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            public void Add(Sequence sequence)
-            {
-                // Code table is full, stop adding new codes
-                if (_count >= _table.Length)
-                    return;
+            Add(Sequence.ClearCode);
+            Add(Sequence.StopCode);
+        }
 
-                _table[_count++] = sequence;
-                if ((_count & (_count - 1)) == 0 && _codeLength < MaxCodeLength)
-                    _codeLength++;
-            }
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Reset()
+        {
+            Count = (1 << _minimumCodeLength) + 2;
+            CodeLength = _minimumCodeLength + 1;
+        }
 
-            public Sequence this[int index]
-            {
-                [MethodImpl(MethodImplOptions.AggressiveInlining)]
-                get
-                {
-                    return _table[index];
-                }
-            }
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Add(Sequence sequence)
+        {
+            // Code table is full, stop adding new codes
+            if (Count >= _table.Length)
+                return;
 
-            public int Count
-            {
-                [MethodImpl(MethodImplOptions.AggressiveInlining)]
-                get { return _count; }
-            }
+            _table[Count++] = sequence;
+            if ((Count & (Count - 1)) == 0 && CodeLength < MaxCodeLength)
+                CodeLength++;
+        }
 
-            public int CodeLength
-            {
-                [MethodImpl(MethodImplOptions.AggressiveInlining)]
-                get { return _codeLength; }
-            }
+        public Sequence this[int index]
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get => _table[index];
+        }
+
+        public int Count
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get;
+            private set;
+        }
+
+        public int CodeLength
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get;
+            private set;
         }
     }
-}
+}

+ 0 - 16
src/XamlAnimatedGif/DownloadProgressEventArgs.cs

@@ -1,16 +0,0 @@
-using System.Windows;
-
-namespace XamlAnimatedGif
-{
-    public delegate void DownloadProgressEventHandler(DependencyObject d, DownloadProgressEventArgs e);
-
-    public class DownloadProgressEventArgs : RoutedEventArgs
-    {
-        public int Progress { get; set; }
-
-        public DownloadProgressEventArgs(object source, int progress) : base(AnimationBehavior.DownloadProgressEvent, source)
-        {
-            Progress = progress;
-        }
-    }
-}

+ 0 - 17
src/XamlAnimatedGif/Extensions/BitArrayExtensions.cs

@@ -1,17 +0,0 @@
-using System.Collections;
-
-namespace XamlAnimatedGif.Extensions
-{
-    static class BitArrayExtensions
-    {
-        public static short ToInt16(this BitArray bitArray)
-        {
-            short n = 0;
-            for (int i = bitArray.Length - 1; i >= 0; i--)
-            {
-                n = (short) ((n << 1) + (bitArray[i] ? 1 : 0));
-            }
-            return n;
-        }
-    }
-}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 44 - 44
src/XamlAnimatedGif/Extensions/StreamExtensions.cs


+ 18 - 20
src/XamlAnimatedGif/Extensions/WritableBitmapExtensions.cs

@@ -1,29 +1,27 @@
-using System;
-using System.Windows.Media.Imaging;
+using System.Windows.Media.Imaging;
 
-namespace XamlAnimatedGif.Extensions
+namespace XamlAnimatedGif.Extensions;
+
+internal static class WritableBitmapExtensions
 {
-    static class WritableBitmapExtensions
+    public static IDisposable LockInScope(this WriteableBitmap bitmap)
+    {
+        return new WriteableBitmapLock(bitmap);
+    }
+
+    private class WriteableBitmapLock : IDisposable
     {
-        public static IDisposable LockInScope(this WriteableBitmap bitmap)
+        private readonly WriteableBitmap _bitmap;
+
+        public WriteableBitmapLock(WriteableBitmap bitmap)
         {
-            return new WriteableBitmapLock(bitmap);
+            _bitmap = bitmap;
+            _bitmap.Lock();
         }
 
-        class WriteableBitmapLock : IDisposable
+        public void Dispose()
         {
-            private readonly WriteableBitmap _bitmap;
-
-            public WriteableBitmapLock(WriteableBitmap bitmap)
-            {
-                _bitmap = bitmap;
-                _bitmap.Lock();
-            }
-
-            public void Dispose()
-            {
-                _bitmap.Unlock();
-            }
+            _bitmap.Unlock();
         }
     }
-}
+}

+ 52 - 44
src/XamlAnimatedGif/ImageAnimator.cs

@@ -1,55 +1,63 @@
 using System;
 using System.IO;
-using System.Threading;
 using System.Threading.Tasks;
 using System.Windows.Controls;
 using System.Windows.Media.Animation;
 using XamlAnimatedGif.Decoding;
 
-namespace XamlAnimatedGif
+namespace XamlAnimatedGif;
+
+internal class ImageAnimator : Animator
 {
-    internal class ImageAnimator : Animator
+    private readonly Image _image;
+
+    public ImageAnimator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior,
+        Image image) : this(sourceStream, sourceUri, metadata, repeatBehavior, image, false)
+    {
+    }
+
+    public ImageAnimator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior,
+        Image image, bool cacheFrameDataInMemory) : base(sourceStream, sourceUri, metadata, repeatBehavior,
+        cacheFrameDataInMemory)
+    {
+        _image = image;
+        OnRepeatBehaviorChanged(); // in case the value has changed during creation
+    }
+
+    protected override RepeatBehavior GetSpecifiedRepeatBehavior()
+    {
+        return AnimationBehavior.GetRepeatBehavior(_image);
+    }
+
+    protected override object AnimationSource => _image;
+
+    public static Task<ImageAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior,
+        IProgress<int> progress, Image image)
+    {
+        return CreateAsync(sourceUri, repeatBehavior, progress, image, false);
+    }
+
+    public static Task<ImageAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior,
+        IProgress<int> progress, Image image, bool cacheFrameDataInMemory)
+    {
+        return CreateAsyncCore(
+            sourceUri,
+            progress,
+            (stream, metadata) => new ImageAnimator(stream, sourceUri, metadata, repeatBehavior, image,
+                cacheFrameDataInMemory));
+    }
+
+    public static Task<ImageAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior, Image image)
+    {
+        return CreateAsync(sourceStream, repeatBehavior, image);
+    }
+
+    public static Task<ImageAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior, Image image,
+        bool cacheFrameDataInMemory)
     {
-        private readonly Image _image;
-
-        public ImageAnimator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior,
-            Image image) : this(sourceStream, sourceUri, metadata, repeatBehavior, image, false, CancellationToken.None)
-        {
-        }
-
-        public ImageAnimator(Stream sourceStream, Uri sourceUri, GifDataStream metadata, RepeatBehavior repeatBehavior, Image image, bool cacheFrameDataInMemory, CancellationToken cancellationToken) : base(sourceStream, sourceUri, metadata, repeatBehavior, cacheFrameDataInMemory, cancellationToken)
-        {
-            _image = image;
-            OnRepeatBehaviorChanged(); // in case the value has changed during creation
-        }
-
-        protected override RepeatBehavior GetSpecifiedRepeatBehavior() => AnimationBehavior.GetRepeatBehavior(_image);
-
-        protected override object AnimationSource => _image;
-
-        public static Task<ImageAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior, IProgress<int> progress, Image image)
-        {
-            return CreateAsync(sourceUri, repeatBehavior, progress, image, false, CancellationToken.None);
-        }
-
-        public static Task<ImageAnimator> CreateAsync(Uri sourceUri, RepeatBehavior repeatBehavior, IProgress<int> progress, Image image, bool cacheFrameDataInMemory, CancellationToken cancellationToken)
-        {
-            return CreateAsyncCore(
-                sourceUri,
-                progress,
-                (stream, metadata) => new ImageAnimator(stream, sourceUri, metadata, repeatBehavior, image, cacheFrameDataInMemory, cancellationToken));
-        }
-
-        public static Task<ImageAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior, Image image)
-        {
-            return CreateAsync(sourceStream, repeatBehavior, image);
-        }
-
-        public static Task<ImageAnimator> CreateAsync(Stream sourceStream, RepeatBehavior repeatBehavior, Image image, bool cacheFrameDataInMemory, CancellationToken cancellationToken)
-        {
-            return CreateAsyncCore(
-                sourceStream,
-                metadata => new ImageAnimator(sourceStream, null, metadata, repeatBehavior, image, cacheFrameDataInMemory, cancellationToken));
-        }
+        return CreateAsyncCore(
+            sourceStream,
+            metadata => new ImageAnimator(sourceStream, null, metadata, repeatBehavior, image,
+                cacheFrameDataInMemory));
     }
 }

+ 4 - 5
src/XamlAnimatedGif/Properties/Xmlns.cs

@@ -4,10 +4,9 @@ using XamlAnimatedGif.Properties;
 [assembly: XmlnsDefinition(XmlnsInfo.XmlNamespace, "XamlAnimatedGif")]
 [assembly: XmlnsPrefix(XmlnsInfo.XmlNamespace, "gif")]
 
-namespace XamlAnimatedGif.Properties
+namespace XamlAnimatedGif.Properties;
+
+internal static class XmlnsInfo
 {
-    static class XmlnsInfo
-    {
-        public const string XmlNamespace = "https://github.com/XamlAnimatedGif/XamlAnimatedGif";
-    }
+    public const string XmlNamespace = "https://github.com/XamlAnimatedGif/XamlAnimatedGif";
 }

+ 92 - 91
src/XamlAnimatedGif/TimingManager.cs

@@ -4,127 +4,128 @@ using System.Threading;
 using System.Threading.Tasks;
 using System.Windows.Media.Animation;
 
-namespace XamlAnimatedGif
+namespace XamlAnimatedGif;
+
+internal class TimingManager
 {
-    class TimingManager
-    {
-        private readonly List<TimeSpan> _timeSpans = new List<TimeSpan>();
-        private int _current;
-        private int _count;
-        private bool _isComplete;
-        private TimeSpan _elapsed;
+    private readonly List<TimeSpan> _timeSpans = new();
+    private int _current;
+    private int _count;
+    private bool _isComplete;
+    private TimeSpan _elapsed;
 
-        public TimingManager(RepeatBehavior repeatBehavior)
-        {
-            RepeatBehavior = repeatBehavior;
-        }
+    public TimingManager(RepeatBehavior repeatBehavior)
+    {
+        RepeatBehavior = repeatBehavior;
+    }
 
-        public RepeatBehavior RepeatBehavior { get; set; }
+    public RepeatBehavior RepeatBehavior { get; set; }
 
-        public void Add(TimeSpan timeSpan)
-        {
-            _timeSpans.Add(timeSpan);
-        }
+    public void Add(TimeSpan timeSpan)
+    {
+        _timeSpans.Add(timeSpan);
+    }
 
-        public async Task<bool> NextAsync(CancellationToken cancellationToken)
-        {
-            if (IsComplete)
-                return false;
+    public async Task<bool> NextAsync(CancellationToken cancellationToken)
+    {
+        if (IsComplete)
+            return false;
 
-            await IsPausedAsync(cancellationToken);
+        await IsPausedAsync(cancellationToken).ConfigureAwait(false);
 
-            var repeatBehavior = RepeatBehavior;
+        var repeatBehavior = RepeatBehavior;
 
-            var ts = _timeSpans[_current];
-            await Task.Delay(ts, cancellationToken);
-            _current++;
-            _elapsed += ts;
+        var ts = _timeSpans[_current];
+        await Task.Delay(ts, cancellationToken);
+        _current++;
+        _elapsed += ts;
 
-            if (repeatBehavior.HasDuration)
+        if (repeatBehavior.HasDuration)
+        {
+            if (_elapsed >= repeatBehavior.Duration)
             {
-                if (_elapsed >= repeatBehavior.Duration)
-                {
-                    IsComplete = true;
-                    return false;
-                }
+                IsComplete = true;
+                return false;
             }
+        }
 
-            if (_current >= _timeSpans.Count)
+        if (_current >= _timeSpans.Count)
+        {
+            _count++;
+            if (repeatBehavior.HasCount)
             {
-                _count++;
-                if (repeatBehavior.HasCount)
-                {
-                    if (_count < repeatBehavior.Count)
-                    {
-                        _current = 0;
-                        return true;
-                    }
-                    IsComplete = true;
-                    return false;
-                }
-                else
+                if (_count < repeatBehavior.Count)
                 {
                     _current = 0;
                     return true;
                 }
+
+                IsComplete = true;
+                return false;
             }
-            return true;
-        }
 
-        public void Reset()
-        {
             _current = 0;
-            _count = 0;
-            _elapsed = TimeSpan.Zero;
-            IsComplete = false;
+            return true;
         }
 
-        public event EventHandler Completed;
+        return true;
+    }
 
-        protected virtual void OnCompleted()
-        {
-            Completed?.Invoke(this, EventArgs.Empty);
-        }
+    public void Reset()
+    {
+        _current = 0;
+        _count = 0;
+        _elapsed = TimeSpan.Zero;
+        IsComplete = false;
+    }
 
-        public bool IsComplete
-        {
-            get { return _isComplete; }
-            private set
-            {
-                _isComplete = value;
-                if (value)
-                    OnCompleted();
-            }
-        }
+    public event EventHandler Completed;
 
-        private readonly Task _completedTask = Task.FromResult(0);
-        private TaskCompletionSource<int> _pauseCompletionSource;
-        public void Pause()
-        {
-            if (IsPaused) return; // Make this a no-op.
-            IsPaused = true;
-            _pauseCompletionSource = new TaskCompletionSource<int>();
-        }
+    protected virtual void OnCompleted()
+    {
+        Completed?.Invoke(this, EventArgs.Empty);
+    }
 
-        public void Resume()
+    public bool IsComplete
+    {
+        get => _isComplete;
+        private set
         {
-            if (!IsPaused) return; // Make this a no-op.
-            var tcs = _pauseCompletionSource;
-            tcs?.TrySetResult(0);
-            _pauseCompletionSource = null;
-            IsPaused = false;
+            _isComplete = value;
+            if (value)
+                OnCompleted();
         }
+    }
 
-        public bool IsPaused { get; private set; }
+    private readonly Task _completedTask = Task.FromResult(0);
+    private TaskCompletionSource<int> _pauseCompletionSource;
 
-        private Task IsPausedAsync(CancellationToken cancellationToken)
+    public void Pause()
+    {
+        if (IsPaused) return; // Make this a no-op.
+        IsPaused = true;
+        _pauseCompletionSource = new TaskCompletionSource<int>();
+    }
+
+    public void Resume()
+    {
+        if (!IsPaused) return; // Make this a no-op.
+        var tcs = _pauseCompletionSource;
+        tcs?.TrySetResult(0);
+        _pauseCompletionSource = null;
+        IsPaused = false;
+    }
+
+    public bool IsPaused { get; private set; }
+
+    private Task IsPausedAsync(CancellationToken cancellationToken)
+    {
+        var tcs = _pauseCompletionSource;
+        if (tcs != null)
         {
-            var tcs = _pauseCompletionSource;
-            if (tcs != null)
-            {
-                return tcs.Task.WithCancellationToken(cancellationToken);
-            }
-            return _completedTask;
+            return tcs.Task.WithCancellationToken(cancellationToken);
         }
+
+        return _completedTask;
     }
-}
+}

+ 59 - 108
src/XamlAnimatedGif/UriLoader.cs

@@ -1,134 +1,85 @@
-using System;
-using System.IO;
+using System.IO;
 using System.IO.Packaging;
-using System.Linq;
 using System.Net.Http;
 using System.Security.Cryptography;
 using System.Text;
-using System.Threading.Tasks;
 using System.Windows;
 using XamlAnimatedGif.Extensions;
 
-namespace XamlAnimatedGif
+namespace XamlAnimatedGif;
+
+internal class UriLoader
 {
-    internal class UriLoader
+    public static Task<Stream> GetStreamFromUriAsync(Uri uri)
     {
-        public static Task<Stream> GetStreamFromUriAsync(Uri uri, IProgress<int> progress)
-        {
-            if (uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https"))
-                return GetNetworkStreamAsync(uri, progress);
-            return GetStreamFromUriCoreAsync(uri);
-        }
-
-        private static async Task<Stream> GetNetworkStreamAsync(Uri uri, IProgress<int> progress)
-        {
-            string cacheFileName = GetCacheFileName(uri);
-            var cacheStream = await OpenTempFileStreamAsync(cacheFileName);
-            if (cacheStream == null)
-            {
-                await DownloadToCacheFileAsync(uri, cacheFileName, progress);
-                cacheStream = await OpenTempFileStreamAsync(cacheFileName);
-            }
-            progress.Report(100);
-            return cacheStream;
-        }
-        private static async Task DownloadToCacheFileAsync(Uri uri, string fileName, IProgress<int> progress)
-        {
-            try
-            {
-                using var client = new HttpClient();
-                var request = new HttpRequestMessage(HttpMethod.Get, uri);
-                var response = await client.SendAsync(request);
-                response.EnsureSuccessStatusCode();
-                long length = response.Content.Headers.ContentLength ?? 0;
-                using var responseStream = await response.Content.ReadAsStreamAsync();
-                using var fileStream = await CreateTempFileStreamAsync(fileName);
-                IProgress<long> absoluteProgress = null;
-                if (progress != null)
-                {
-                    absoluteProgress =
-                        new Progress<long>(bytesCopied =>
-                        {
-                            if (length > 0)
-                                progress.Report((int)(100 * bytesCopied / length));
-                            else
-                                progress.Report(-1);
-                        });
-                }
-                await responseStream.CopyToAsync(fileStream, absoluteProgress);
-            }
-            catch
-            {
-                DeleteTempFile(fileName);
-                throw;
-            }
-        }
+        return GetStreamFromUriCoreAsync(uri);
+    }
 
-        private static Task<Stream> GetStreamFromUriCoreAsync(Uri uri)
+    private static Task<Stream> GetStreamFromUriCoreAsync(Uri uri)
+    {
+        if (uri.Scheme == PackUriHelper.UriSchemePack)
         {
-            if (uri.Scheme == PackUriHelper.UriSchemePack)
-            {
-                var sri = uri.Authority == "siteoforigin:,,,"
-                    ? Application.GetRemoteStream(uri)
-                    : Application.GetResourceStream(uri);
-
-                if (sri != null)
-                    return Task.FromResult(sri.Stream);
-
-                throw new FileNotFoundException("Cannot find file with the specified URI");
-            }
+            var sri = uri.Authority == "siteoforigin:,,,"
+                ? Application.GetRemoteStream(uri)
+                : Application.GetResourceStream(uri);
 
-            if (uri.Scheme == Uri.UriSchemeFile)
-            {
-                return Task.FromResult<Stream>(File.OpenRead(uri.LocalPath));
-            }
+            if (sri != null)
+                return Task.FromResult(sri.Stream);
 
-            throw new NotSupportedException("Only pack:, file:, http: and https: URIs are supported");
+            throw new FileNotFoundException("Cannot find file with the specified URI");
         }
 
-        private static Task<Stream> OpenTempFileStreamAsync(string fileName)
+        if (uri.Scheme == Uri.UriSchemeFile)
         {
-            string path = Path.Combine(Path.GetTempPath(), fileName);
-            Stream stream = null;
-            try
-            {
-                stream = File.OpenRead(path);
-            }
-            catch (FileNotFoundException)
-            {
-            }
-            return Task.FromResult(stream);
+            return Task.FromResult<Stream>(File.OpenRead(uri.LocalPath));
         }
 
-        private static Task<Stream> CreateTempFileStreamAsync(string fileName)
-        {
-            string path = Path.Combine(Path.GetTempPath(), fileName);
-            Stream stream = File.OpenWrite(path);
-            stream.SetLength(0);
-            return Task.FromResult(stream);
-        }
+        throw new NotSupportedException("Only pack:, file:, http: and https: URIs are supported");
+    }
 
-        private static void DeleteTempFile(string fileName)
+    private static Task<Stream> OpenTempFileStreamAsync(string fileName)
+    {
+        var path = Path.Combine(Path.GetTempPath(), fileName);
+        Stream stream = null;
+        try
         {
-            string path = Path.Combine(Path.GetTempPath(), fileName);
-            if (File.Exists(path))
-                File.Delete(path);
+            stream = File.OpenRead(path);
         }
-
-        private static string GetCacheFileName(Uri uri)
+        catch (FileNotFoundException)
         {
-            using var sha1 = SHA1.Create();
-            var bytes = Encoding.UTF8.GetBytes(uri.AbsoluteUri);
-            var hash = sha1.ComputeHash(bytes);
-            return ToHex(hash);
         }
 
-        private static string ToHex(byte[] bytes)
-        {
-            return bytes.Aggregate(
-                new StringBuilder(),
-                (sb, b) => sb.Append(b.ToString("X2")),
-                sb => sb.ToString());
-        }
+        return Task.FromResult(stream);
+    }
+
+    private static Task<Stream> CreateTempFileStreamAsync(string fileName)
+    {
+        var path = Path.Combine(Path.GetTempPath(), fileName);
+        Stream stream = File.OpenWrite(path);
+        stream.SetLength(0);
+        return Task.FromResult(stream);
+    }
+
+    private static void DeleteTempFile(string fileName)
+    {
+        var path = Path.Combine(Path.GetTempPath(), fileName);
+        if (File.Exists(path))
+            File.Delete(path);
+    }
+
+    private static string GetCacheFileName(Uri uri)
+    {
+        using var sha1 = SHA1.Create();
+        var bytes = Encoding.UTF8.GetBytes(uri.AbsoluteUri);
+        var hash = sha1.ComputeHash(bytes);
+        return ToHex(hash);
+    }
+
+    private static string ToHex(byte[] bytes)
+    {
+        return bytes.Aggregate(
+            new StringBuilder(),
+            (sb, b) => sb.Append(b.ToString("X2")),
+            sb => sb.ToString());
     }
 }

+ 201 - 0
src/XamlAnimatedGif/XamlAnimatedGif LICENSE.txt

@@ -0,0 +1,201 @@
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 4 - 4
src/XamlAnimatedGif/XamlAnimatedGif.csproj

@@ -30,11 +30,11 @@
     <Reference Include="System.Net.Http" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="InternalsVisibleTo.MSBuild" Version="1.0.4" PrivateAssets="All" />
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
-    <PackageReference Include="MinVer" Version="2.3.1" PrivateAssets="All" />
+    <InternalsVisibleTo Include="XamlAnimatedGif.Demo, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5fb0928fd3a0e9d56701d22ef2d7f38ed87ba03ef594ddd5eccc269b64029d7b85d775f112a88394a7d0155b54987a5f2614a08ad0ec34c61431b05ab239afe2c6f2be909ac635dad6240af73934792b62bfe88a57b4a03275818dc304678dd6d22654b0b425165ce000eacdd7a0f2b9ac10e6e6db6e40db4e888ae1fbeebc0" />
   </ItemGroup>
   <ItemGroup>
-    <InternalsVisibleTo Include="XamlAnimatedGif.Demo, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5fb0928fd3a0e9d56701d22ef2d7f38ed87ba03ef594ddd5eccc269b64029d7b85d775f112a88394a7d0155b54987a5f2614a08ad0ec34c61431b05ab239afe2c6f2be909ac635dad6240af73934792b62bfe88a57b4a03275818dc304678dd6d22654b0b425165ce000eacdd7a0f2b9ac10e6e6db6e40db4e888ae1fbeebc0" />
+    <None Update="XamlAnimatedGif LICENSE.txt">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 </Project>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů