diff --git a/src/Avalonia.Visuals/Animation/ColorAnimator.cs b/src/Avalonia.Visuals/Animation/ColorAnimator.cs
new file mode 100644
index 0000000000..cfcbfd62e6
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/ColorAnimator.cs
@@ -0,0 +1,68 @@
+// Original source was written by Romain Guy and Francois Blavoet
+// and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp).
+
+using System;
+using Avalonia.Logging;
+using Avalonia.Media;
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Animator that interpolates through
+ /// gamma sRGB color space for better visual result.
+ ///
+ public class ColorAnimator : Animator
+ {
+ // Opto-electronic conversion function for the sRGB color space
+ // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
+ private static double OECF_sRGB(double linear)
+ {
+ // IEC 61966-2-1:1999
+ return linear <= 0.0031308d ? linear * 12.92d : (double)(Math.Pow(linear, 1.0d / 2.4d) * 1.055d - 0.055d);
+ }
+
+ // Electro-optical conversion function for the sRGB color space
+ // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
+ private static double EOCF_sRGB(double srgb)
+ {
+ // IEC 61966-2-1:1999
+ return srgb <= 0.04045d ? srgb / 12.92d : (double)Math.Pow((srgb + 0.055d) / 1.055d, 2.4d);
+ }
+
+ protected override Color Interpolate(double fraction, Color start, Color end)
+ {
+ var startA = start.A / 255d;
+ var startR = start.R / 255d;
+ var startG = start.G / 255d;
+ var startB = start.B / 255d;
+
+ var endA = end.A / 255d;
+ var endR = end.R / 255d;
+ var endG = end.G / 255d;
+ var endB = end.B / 255d;
+
+ // convert from sRGB to linear
+ startR = EOCF_sRGB(startR);
+ startG = EOCF_sRGB(startG);
+ startB = EOCF_sRGB(startB);
+
+ endR = EOCF_sRGB(endR);
+ endG = EOCF_sRGB(endG);
+ endB = EOCF_sRGB(endB);
+
+ // compute the interpolated color in linear space
+ var a = startA + fraction * (endA - startA);
+ var r = startR + fraction * (endR - startR);
+ var g = startG + fraction * (endG - startG);
+ var b = startB + fraction * (endB - startB);
+
+ // convert back to sRGB in the [0..255] range
+ a = a * 255.0d;
+ r = OECF_sRGB(r) * 255.0d;
+ g = OECF_sRGB(g) * 255.0d;
+ b = OECF_sRGB(b) * 255.0d;
+
+ return new Color((byte)Math.Round(r), (byte)Math.Round(g), (byte)Math.Round(b), (byte)Math.Round(a));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index 8294934751..263e5adc4e 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -3,6 +3,7 @@
using System;
using System.Globalization;
+using Avalonia.Animation;
namespace Avalonia.Media
{
@@ -11,6 +12,11 @@ namespace Avalonia.Media
///
public readonly struct Color
{
+ static Color()
+ {
+ Animation.Animation.RegisterAnimator(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
+ }
+
///
/// Gets or sets the Alpha component of the color.
///