diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/Colors/Spaces/CieLab.cs index f4f349b52..a4b9cb5f4 100644 --- a/src/ImageSharp/Colors/Spaces/CieLab.cs +++ b/src/ImageSharp/Colors/Spaces/CieLab.cs @@ -161,7 +161,7 @@ namespace ImageSharp.Colors.Spaces /// public bool Equals(CieLab other) { - return this.AlmostEquals(other, Constants.Epsilon); + return this.backingVector.Equals(other.backingVector); } /// @@ -169,9 +169,9 @@ namespace ImageSharp.Colors.Spaces { Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - return result.X < precision - && result.Y < precision - && result.Z < precision; + return result.X <= precision + && result.Y <= precision + && result.Z <= precision; } } } diff --git a/src/ImageSharp/Colors/Spaces/CieXyz.cs b/src/ImageSharp/Colors/Spaces/CieXyz.cs index 49396d3f6..5bd1eac63 100644 --- a/src/ImageSharp/Colors/Spaces/CieXyz.cs +++ b/src/ImageSharp/Colors/Spaces/CieXyz.cs @@ -152,7 +152,7 @@ namespace ImageSharp.Colors.Spaces /// public bool Equals(CieXyz other) { - return this.AlmostEquals(other, Constants.Epsilon); + return this.backingVector.Equals(other.backingVector); } /// @@ -160,9 +160,9 @@ namespace ImageSharp.Colors.Spaces { Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - return result.X < precision - && result.Y < precision - && result.Z < precision; + return result.X <= precision + && result.Y <= precision + && result.Z <= precision; } } } diff --git a/src/ImageSharp/Colors/Spaces/Cmyk.cs b/src/ImageSharp/Colors/Spaces/Cmyk.cs index 190d73598..82e749f39 100644 --- a/src/ImageSharp/Colors/Spaces/Cmyk.cs +++ b/src/ImageSharp/Colors/Spaces/Cmyk.cs @@ -162,7 +162,7 @@ namespace ImageSharp.Colors.Spaces /// public bool Equals(Cmyk other) { - return this.AlmostEquals(other, Constants.Epsilon); + return this.backingVector.Equals(other.backingVector); } /// @@ -170,10 +170,10 @@ namespace ImageSharp.Colors.Spaces { Vector4 result = Vector4.Abs(this.backingVector - other.backingVector); - return result.X < precision - && result.Y < precision - && result.Z < precision - && result.W < precision; + return result.X <= precision + && result.Y <= precision + && result.Z <= precision + && result.W <= precision; } } } diff --git a/src/ImageSharp/Colors/Spaces/Hsl.cs b/src/ImageSharp/Colors/Spaces/Hsl.cs index 2cb02107b..13973349c 100644 --- a/src/ImageSharp/Colors/Spaces/Hsl.cs +++ b/src/ImageSharp/Colors/Spaces/Hsl.cs @@ -181,7 +181,7 @@ namespace ImageSharp.Colors.Spaces /// public bool Equals(Hsl other) { - return this.AlmostEquals(other, Constants.Epsilon); + return this.backingVector.Equals(other.backingVector); } /// @@ -189,9 +189,9 @@ namespace ImageSharp.Colors.Spaces { Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - return result.X < precision - && result.Y < precision - && result.Z < precision; + return result.X <= precision + && result.Y <= precision + && result.Z <= precision; } } } diff --git a/src/ImageSharp/Colors/Spaces/Hsv.cs b/src/ImageSharp/Colors/Spaces/Hsv.cs index 8f7ebbdc7..f7ed05b86 100644 --- a/src/ImageSharp/Colors/Spaces/Hsv.cs +++ b/src/ImageSharp/Colors/Spaces/Hsv.cs @@ -174,7 +174,7 @@ namespace ImageSharp.Colors.Spaces /// public bool Equals(Hsv other) { - return this.AlmostEquals(other, Constants.Epsilon); + return this.backingVector.Equals(other.backingVector); } /// @@ -182,9 +182,9 @@ namespace ImageSharp.Colors.Spaces { Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - return result.X < precision - && result.Y < precision - && result.Z < precision; + return result.X <= precision + && result.Y <= precision + && result.Z <= precision; } } } diff --git a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs index af948fa21..c241e8d6f 100644 --- a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Tests.Colors { using System; using System.Numerics; + using ImageSharp.Colors.Spaces; using Xunit; /// @@ -37,6 +38,38 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.One * 0x7FFF), typeof(Short4) }, }; + public static readonly TheoryData EqualityDataColorSpaces = + new TheoryData() + { + { new Bgra32(0, 0, 0), new Bgra32(0, 0, 0), typeof(Bgra32) }, + { new Bgra32(0, 0, 0, 0), new Bgra32(0, 0, 0, 0), typeof(Bgra32) }, + { new Bgra32(100, 100, 0, 0), new Bgra32(100, 100, 0, 0), typeof(Bgra32) }, + { new Bgra32(255, 255, 255), new Bgra32(255, 255, 255), typeof(Bgra32) }, + { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab) }, + { new CieLab(1f, 1f, 1f), new CieLab(1f, 1f, 1f), typeof(CieLab) }, + { new CieLab(0f, -100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, + { new CieLab(0f, 100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, + { new CieLab(0f, -100f, 100f), new CieLab(0f, -100f, 100f), typeof(CieLab) }, + { new CieLab(0f, -100f, 50f), new CieLab(0f, -100f, 50f), typeof(CieLab) }, + { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz) }, + { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 780f, 780f), typeof(CieXyz) }, + { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 780f, 780f), typeof(CieXyz) }, + { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 20f, 60f), typeof(CieXyz) }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk) }, + { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 1f, 1f), typeof(Cmyk) }, + { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 10f, 10f), typeof(Cmyk) }, + { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, .1f, .2f), typeof(Cmyk) }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl) }, + { new Hsl(360f, 1f, 1f), new Hsl(360f, 1f, 1f), typeof(Hsl) }, + { new Hsl(100f, .5f, .1f), new Hsl(100f, .5f, .1f), typeof(Hsl) }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv) }, + { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv) }, + { new Hsv(100f, .5f, .1f), new Hsv(100f, .5f, .1f), typeof(Hsv) }, + { new YCbCr(0, 0, 0), new YCbCr(0, 0, 0), typeof(YCbCr) }, + { new YCbCr(255, 255, 255), new YCbCr(255, 255, 255), typeof(YCbCr) }, + { new YCbCr(100, 100, 0), new YCbCr(100, 100, 0), typeof(YCbCr) }, + }; + public static readonly TheoryData NotEqualityDataNulls = new TheoryData() { @@ -61,6 +94,18 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), null, typeof(Short4) }, }; + public static readonly TheoryData NotEqualityDataNullsColorSpaces = + new TheoryData() + { + { new Bgra32(0, 0, 0), null, typeof(Bgra32) }, + { new CieLab(0f, 0f, 0f), null, typeof(CieLab) }, + { new CieXyz(380f, 380f, 380f), null, typeof(CieXyz) }, + { new Cmyk(0f, 0f, 0f, 0f), null, typeof(Cmyk) }, + { new Hsl(0f, 0f, 0f), null, typeof(Hsl) }, + { new Hsv(360f, 1f, 1f), null, typeof(Hsv) }, + { new YCbCr(0, 0, 0), null, typeof(YCbCr) }, + }; + public static readonly TheoryData NotEqualityDataDifferentObjects = new TheoryData() { @@ -70,6 +115,16 @@ namespace ImageSharp.Tests.Colors { new Rgba1010102(Vector4.One), new Bgra5551(Vector4.Zero), null }, }; + public static readonly TheoryData NotEqualityDataDifferentObjectsColorSpaces = + new TheoryData() + { + // Valid objects of different types but not equal + { new Bgra32(0, 0, 0), new CieLab(0f, 0f, 0f), null }, + { new CieXyz(380f, 380f, 380f), new Cmyk(0f, 0f, 0f, 0f), null }, + { new Hsl(0f, 0f, 0f), new Hsv(360f, 1f, 1f), null }, + { new YCbCr(0, 0, 0), new Hsv(360f, 1f, 1f), null }, + }; + public static readonly TheoryData NotEqualityData = new TheoryData() { @@ -94,8 +149,92 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.Zero), typeof(Short4) }, }; + public static readonly TheoryData NotEqualityDataColorSpaces = + new TheoryData() + { + { new Bgra32(0, 0, 0), new Bgra32(0, 1, 0), typeof(Bgra32) }, + { new Bgra32(0, 0, 0, 0), new Bgra32(0, 1, 0, 0), typeof(Bgra32) }, + { new Bgra32(100, 100, 0, 0), new Bgra32(100, 0, 0, 0), typeof(Bgra32) }, + { new Bgra32(255, 255, 255), new Bgra32(255, 0, 255), typeof(Bgra32) }, + { new CieLab(0f, 0f, 0f), new CieLab(0f, 1f, 0f), typeof(CieLab) }, + { new CieLab(1f, 1f, 1f), new CieLab(1f, 0f, 1f), typeof(CieLab) }, + { new CieLab(0f, -100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, + { new CieLab(0f, 100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, + { new CieLab(0f, -100f, 100f), new CieLab(0f, 100f, 100f), typeof(CieLab) }, + { new CieLab(0f, -100f, 50f), new CieLab(0f, 100f, 20f), typeof(CieLab) }, + { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 0f, 380f), typeof(CieXyz) }, + { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 0f, 780f), typeof(CieXyz) }, + { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 0f, 780f), typeof(CieXyz) }, + { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 0f, 60f), typeof(CieXyz) }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 1f, 0f, 0f), typeof(Cmyk) }, + { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 0f, 1f), typeof(Cmyk) }, + { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 0f, 10f), typeof(Cmyk) }, + { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, 5f, .2f), typeof(Cmyk) }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 5f, 0f), typeof(Hsl) }, + { new Hsl(360f, 1f, 1f), new Hsl(360f, .5f, 1f), typeof(Hsl) }, + { new Hsl(100f, .5f, .1f), new Hsl(100f, 9f, .1f), typeof(Hsl) }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 1f, 0f), typeof(Hsv) }, + { new Hsv(360f, 1f, 1f), new Hsv(0f, 1f, 1f), typeof(Hsv) }, + { new Hsv(100f, .5f, .1f), new Hsv(2f, .5f, .1f), typeof(Hsv) }, + { new YCbCr(0, 0, 0), new YCbCr(0, 1, 0), typeof(YCbCr) }, + { new YCbCr(255, 255, 255), new YCbCr(255, 0, 255), typeof(YCbCr) }, + { new YCbCr(100, 100, 0), new YCbCr(100, 20, 0), typeof(YCbCr) }, + }; + + 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 }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk), 0f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.001f, 0f, 0f, 0f), typeof(Cmyk), .01f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.001f, 0f, 0f), typeof(Cmyk), .01f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.001f, 0f), typeof(Cmyk), .01f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.001f), typeof(Cmyk), .01f }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl), 0f }, + { new Hsl(0f, 0f, 0f), new Hsl(0.001f, 0f, 0f), typeof(Hsl), .01f }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.001f, 0f), typeof(Hsl), .01f }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.001f), typeof(Hsl), .01f }, + { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv), 0f }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv), 0f }, + { new Hsv(0f, 0f, 0f), new Hsv(0.001f, 0f, 0f), typeof(Hsv), .01f }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.001f, 0f), typeof(Hsv), .01f }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.001f), typeof(Hsv), .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 }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.1f, 0f, 0f, 0f), typeof(Cmyk), .001f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.1f, 0f, 0f), typeof(Cmyk), .001f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.1f, 0f), typeof(Cmyk), .001f }, + { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.1f), typeof(Cmyk), .001f }, + { new Hsl(0f, 0f, 0f), new Hsl(0.1f, 0f, 0f), typeof(Hsl), .001f }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.1f, 0f), typeof(Hsl), .001f }, + { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.1f), typeof(Hsl), .001f }, + { new Hsv(0f, 0f, 0f), new Hsv(0.1f, 0f, 0f), typeof(Hsv), .001f }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.1f, 0f), typeof(Hsv), .001f }, + { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.1f), typeof(Hsv), .001f }, + }; + [Theory] [MemberData(nameof(EqualityData))] + [MemberData(nameof(EqualityDataColorSpaces))] public void Equality(object first, object second, Type type) { // Act @@ -107,8 +246,11 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataNulls))] + [MemberData(nameof(NotEqualityDataNullsColorSpaces))] [MemberData(nameof(NotEqualityDataDifferentObjects))] + [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] [MemberData(nameof(NotEqualityData))] + [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEquality(object first, object second, Type type) { // Act @@ -120,6 +262,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] + [MemberData(nameof(EqualityDataColorSpaces))] public void HashCodeEqual(object first, object second, Type type) { // Act @@ -131,6 +274,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataDifferentObjects))] + [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] public void HashCodeNotEqual(object first, object second, Type type) { // Act @@ -142,6 +286,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] + [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityObject(object first, object second, Type type) { // Arrange @@ -160,6 +305,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] + [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityObject(object first, object second, Type type) { // Arrange @@ -178,6 +324,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] + [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityOperator(object first, object second, Type type) { // Arrange @@ -196,6 +343,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] + [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityOperator(object first, object second, Type type) { // Arrange @@ -211,5 +359,41 @@ namespace ImageSharp.Tests.Colors // 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 + var 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 + var almostEqual = firstObject.AlmostEquals(secondObject, precision); + + // Assert + Assert.False(almostEqual); + } } }