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