From 3d016041fe4a916d7e0ee4a7b010c936c133cd27 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Thu, 18 Oct 2018 14:36:18 -0700 Subject: [PATCH] Add image animation support - RTimer used to create a timer to update the display - RImage.Frames and SetActiveFrame added --- Source/HtmlRenderer/Adapters/RAdapter.cs | 6 ++ Source/HtmlRenderer/Adapters/RImage.cs | 20 ++++++- Source/HtmlRenderer/Adapters/RImageFrame.cs | 29 ++++++++++ Source/HtmlRenderer/Adapters/RTimer.cs | 58 +++++++++++++++++++ .../Core/Handlers/ImageLoadHandler.cs | 47 ++++++++++++++- 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 Source/HtmlRenderer/Adapters/RImageFrame.cs create mode 100644 Source/HtmlRenderer/Adapters/RTimer.cs diff --git a/Source/HtmlRenderer/Adapters/RAdapter.cs b/Source/HtmlRenderer/Adapters/RAdapter.cs index 6b13459c1..9d7c91b8c 100644 --- a/Source/HtmlRenderer/Adapters/RAdapter.cs +++ b/Source/HtmlRenderer/Adapters/RAdapter.cs @@ -326,6 +326,7 @@ internal RFont CreateFont(RFontFamily family, double size, RFontStyle style) return CreateFontInt(family, size, style); } + public RTimer CreateTimer() => CreateTimerInt(); #region Private/Protected methods @@ -454,6 +455,11 @@ protected virtual void SaveToFileInt(RImage image, string name, string extension throw new NotImplementedException(); } + protected virtual RTimer CreateTimerInt() + { + throw new NotImplementedException(); + } + #endregion } } \ No newline at end of file diff --git a/Source/HtmlRenderer/Adapters/RImage.cs b/Source/HtmlRenderer/Adapters/RImage.cs index 6c184e2d1..8dc2b19fc 100644 --- a/Source/HtmlRenderer/Adapters/RImage.cs +++ b/Source/HtmlRenderer/Adapters/RImage.cs @@ -11,6 +11,7 @@ // "The Art of War" using System; +using System.Collections.Generic; namespace TheArtOfDev.HtmlRenderer.Adapters { @@ -29,6 +30,23 @@ public abstract class RImage : IDisposable /// public abstract double Height { get; } + /// + /// Gets the frames of the image, or null if animation is not supported. + /// + /// + /// Non-animated images may return a single frame. + /// + /// The frames. + public virtual IList Frames => null; + + /// + /// Sets the active frame to display when painted. + /// + /// Frame to display for this image. + public virtual void SetActiveFrame(int frame) + { + } + public abstract void Dispose(); } -} \ No newline at end of file +} diff --git a/Source/HtmlRenderer/Adapters/RImageFrame.cs b/Source/HtmlRenderer/Adapters/RImageFrame.cs new file mode 100644 index 000000000..43926c068 --- /dev/null +++ b/Source/HtmlRenderer/Adapters/RImageFrame.cs @@ -0,0 +1,29 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +using System; +using System.Collections.Generic; + +namespace TheArtOfDev.HtmlRenderer.Adapters +{ + /// + /// Adapter for platform specific image frame object - used when rendering animated images. + /// + public abstract class RImageFrame + { + /// + /// Gets the delay of this frame + /// + /// The frame delay. + public abstract TimeSpan FrameDelay { get; } + } +} \ No newline at end of file diff --git a/Source/HtmlRenderer/Adapters/RTimer.cs b/Source/HtmlRenderer/Adapters/RTimer.cs new file mode 100644 index 000000000..e5e7a27ae --- /dev/null +++ b/Source/HtmlRenderer/Adapters/RTimer.cs @@ -0,0 +1,58 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +using System; + +namespace TheArtOfDev.HtmlRenderer.Adapters +{ + /// + /// Adapter for firing a timer on the UI thread. + /// + /// + /// The timer should call the method repeatedly at the + /// specified , until is called. + /// + public abstract class RTimer : IDisposable + { + /// + /// Occurs when the timer has elapsed. + /// + public event EventHandler Elapsed; + + /// + /// Invokes the event. + /// + /// Event arguments + protected virtual void OnElapsed(EventArgs e) => Elapsed?.Invoke(this, e); + + /// + /// Gets or sets the interval between timer invocations. + /// + /// The interval for the timer. + public abstract TimeSpan Interval { get; set; } + + /// + /// Starts the timer. + /// + public abstract void Start(); + + /// + /// Stops the timer. + /// + public abstract void Stop(); + + /// + /// Releases all resources used by the object. + /// + public abstract void Dispose(); + } +} diff --git a/Source/HtmlRenderer/Core/Handlers/ImageLoadHandler.cs b/Source/HtmlRenderer/Core/Handlers/ImageLoadHandler.cs index eb9ace4fd..55b1017ec 100644 --- a/Source/HtmlRenderer/Core/Handlers/ImageLoadHandler.cs +++ b/Source/HtmlRenderer/Core/Handlers/ImageLoadHandler.cs @@ -84,8 +84,15 @@ internal sealed class ImageLoadHandler : IDisposable /// private bool _disposed; - #endregion + /// + /// Timer for animated images + /// + private RTimer _timer; + + private int _currentFrame; + + #endregion /// /// Init. @@ -365,7 +372,14 @@ private void ImageLoadComplete(bool async = true) if (_disposed) ReleaseObjects(); else + { + if (_image.Frames?.Count > 1) + { + // it is an animated image, begin animating! + CreateAnimationTimer(); + } _loadCompleteCallback(_image, _imageRectangle, async); + } } /// @@ -375,6 +389,12 @@ private void ReleaseObjects() { lock (_loadCompleteCallback) { + if (_timer != null) + { + _timer.Stop(); + _timer.Dispose(); + _timer = null; + } if (_releaseImageObject && _image != null) { _image.Dispose(); @@ -388,6 +408,31 @@ private void ReleaseObjects() } } + private void CreateAnimationTimer() + { + if (_timer != null) + return; + + _currentFrame = 0; + _timer = _htmlContainer.Adapter.CreateTimer(); + _timer.Interval = _image.Frames[_currentFrame].FrameDelay; + _timer.Elapsed += timer_Elapsed; + _timer.Start(); + } + + private void timer_Elapsed(object sender, EventArgs e) + { + _currentFrame++; + if (_currentFrame >= _image.Frames.Count) + _currentFrame = 0; + _image.SetActiveFrame(_currentFrame); + + _timer.Interval = _image.Frames[_currentFrame].FrameDelay; + _timer.Start(); + _htmlContainer.RequestRefresh(false); + } + #endregion + } } \ No newline at end of file