diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
new file mode 100644
index 000000000..1ea4d3ce5
--- /dev/null
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
@@ -0,0 +1,237 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests.Colors
+{
+ using System;
+ using System.Numerics;
+ using Xunit;
+
+ using ImageSharp.Colors.Spaces;
+
+ ///
+ /// Test implementations of IEquatable and IAlmostEquatable in our colorspaces
+ ///
+ public class ColorSpaceEqualityTests
+ {
+ public static readonly TheoryData EqualityData =
+ new TheoryData()
+ {
+ { new CieLab(Vector3.One), new CieLab(Vector3.One), typeof(CieLab) },
+ { new CieLch(Vector3.One), new CieLch(Vector3.One), typeof(CieLch) },
+ { new CieXyz(Vector3.One), new CieXyz(Vector3.One), typeof(CieXyz) },
+ };
+
+ public static readonly TheoryData NotEqualityDataNulls =
+ new TheoryData()
+ {
+ // Valid object against null
+ { new CieLab(Vector3.One), null, typeof(CieLab) },
+ { new CieLch(Vector3.One), null, typeof(CieLch) },
+ { new CieXyz(Vector3.One), null, typeof(CieXyz) },
+ };
+
+ public static readonly TheoryData NotEqualityDataDifferentObjects =
+ new TheoryData()
+ {
+ // Valid objects of different types but not equal
+ { new CieLab(Vector3.One), new CieLch(Vector3.Zero), null },
+ { new CieXyz(Vector3.One), new HunterLab(Vector3.Zero), null },
+ { new Rgb(Vector3.One), new LinearRgb(Vector3.Zero), null },
+ { new Rgb(Vector3.One), new Lms(Vector3.Zero), null },
+ };
+
+ public static readonly TheoryData NotEqualityData =
+ new TheoryData()
+ {
+ // Valid objects of the same type but not equal
+ { new CieLab(Vector3.One), new CieLab(Vector3.Zero), typeof(CieLab) },
+ { new CieLch(Vector3.One), new CieLch(Vector3.Zero), typeof(CieLch) },
+ { new CieXyz(Vector3.One), new CieXyz(Vector3.Zero), typeof(CieXyz) },
+ };
+
+ public static readonly TheoryData AlmostEqualsData =
+ new TheoryData()
+ {
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), 0F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .001F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0001F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0005F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, .001F, 0F), typeof(CieLab), .001F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, .0001F), typeof(CieLab), .0001F },
+ { new CieLab(0F, 0F, 0F), new CieLab(.0005F, 0F, 0F), typeof(CieLab), .0005F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380F), typeof(CieXyz), 0F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380.001F, 380F, 380F), typeof(CieXyz), .01F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.001F, 380F), typeof(CieXyz), .01F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.001F), typeof(CieXyz), .01F },
+ };
+
+ public static readonly TheoryData AlmostNotEqualsData =
+ new TheoryData()
+ {
+ { new CieLab(0F, 0F, 0F), new CieLab(0.1F, 0F, 0F), typeof(CieLab), .001F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0.1F, 0F), typeof(CieLab), .001F },
+ { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0.1F), typeof(CieLab), .001F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380.1F, 380F, 380F), typeof(CieXyz), .001F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.1F, 380F), typeof(CieXyz), .001F },
+ { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.1F), typeof(CieXyz), .001F },
+ };
+
+ [Theory]
+ [MemberData(nameof(EqualityData))]
+ public void Equality(object first, object second, Type type)
+ {
+ // Act
+ bool equal = first.Equals(second);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(NotEqualityDataNulls))]
+ [MemberData(nameof(NotEqualityDataDifferentObjects))]
+ [MemberData(nameof(NotEqualityData))]
+ public void NotEquality(object first, object second, Type type)
+ {
+ // Act
+ bool equal = first.Equals(second);
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(EqualityData))]
+ public void HashCodeEqual(object first, object second, Type type)
+ {
+ // Act
+ bool equal = first.GetHashCode() == second.GetHashCode();
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(NotEqualityDataDifferentObjects))]
+ public void HashCodeNotEqual(object first, object second, Type type)
+ {
+ // Act
+ bool equal = first.GetHashCode() == second.GetHashCode();
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(EqualityData))]
+ public void EqualityObject(object first, object second, Type type)
+ {
+ // Arrange
+ // Cast to the known object types, this is so that we can hit the
+ // equality operator on the concrete type, otherwise it goes to the
+ // default "object" one :)
+ dynamic firstObject = Convert.ChangeType(first, type);
+ dynamic secondObject = Convert.ChangeType(second, type);
+
+ // Act
+ dynamic equal = firstObject.Equals(secondObject);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(NotEqualityData))]
+ public void NotEqualityObject(object first, object second, Type type)
+ {
+ // Arrange
+ // Cast to the known object types, this is so that we can hit the
+ // equality operator on the concrete type, otherwise it goes to the
+ // default "object" one :)
+ dynamic firstObject = Convert.ChangeType(first, type);
+ dynamic secondObject = Convert.ChangeType(second, type);
+
+ // Act
+ dynamic equal = firstObject.Equals(secondObject);
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(EqualityData))]
+ public void EqualityOperator(object first, object second, Type type)
+ {
+ // Arrange
+ // Cast to the known object types, this is so that we can hit the
+ // equality operator on the concrete type, otherwise it goes to the
+ // default "object" one :)
+ dynamic firstObject = Convert.ChangeType(first, type);
+ dynamic secondObject = Convert.ChangeType(second, type);
+
+ // Act
+ dynamic equal = firstObject == secondObject;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Theory]
+ [MemberData(nameof(NotEqualityData))]
+ public void NotEqualityOperator(object first, object second, Type type)
+ {
+ // Arrange
+ // Cast to the known object types, this is so that we can hit the
+ // equality operator on the concrete type, otherwise it goes to the
+ // default "object" one :)
+ dynamic firstObject = Convert.ChangeType(first, type);
+ dynamic secondObject = Convert.ChangeType(second, type);
+
+ // Act
+ dynamic notEqual = firstObject != secondObject;
+
+ // Assert
+ Assert.True(notEqual);
+ }
+
+ [Theory]
+ [MemberData(nameof(AlmostEqualsData))]
+ public void AlmostEquals(object first, object second, Type type, float precision)
+ {
+ // Arrange
+ // Cast to the known object types, this is so that we can hit the
+ // equality operator on the concrete type, otherwise it goes to the
+ // default "object" one :)
+ dynamic firstObject = Convert.ChangeType(first, type);
+ dynamic secondObject = Convert.ChangeType(second, type);
+
+ // Act
+ dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision);
+
+ // Assert
+ Assert.True(almostEqual);
+ }
+
+ [Theory]
+ [MemberData(nameof(AlmostNotEqualsData))]
+ public void AlmostNotEquals(object first, object second, Type type, float precision)
+ {
+ // Arrange
+ // Cast to the known object types, this is so that we can hit the
+ // equality operator on the concrete type, otherwise it goes to the
+ // default "object" one :)
+ dynamic firstObject = Convert.ChangeType(first, type);
+ dynamic secondObject = Convert.ChangeType(second, type);
+
+ // Act
+ dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision);
+
+ // Assert
+ Assert.False(almostEqual);
+ }
+
+ }
+}