From c58be60c754fc40264d9f9640d6f6160268d57ce Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 19 Feb 2023 20:46:18 +1000 Subject: [PATCH] Add additional PD tests --- .../PixelBlenders/PorterDuffFunctions.cs | 2 +- .../PixelBlenders/PorterDuffFunctionsTests.cs | 106 +++++++++++++++++- .../TestUtilities/ApproximateFloatComparer.cs | 17 ++- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index af111849a8..d1bd5bad31 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -102,7 +102,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Subtract(Vector256 backdrop, Vector256 source) - => Avx.Min(Vector256.Create(1F), Avx.Subtract(backdrop, source)); + => Avx.Max(Vector256.Zero, Avx.Subtract(backdrop, source)); /// /// Returns the result of the "Screen" compositing equation. diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index 02b4b0ea50..189d21f1e6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -10,6 +10,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; public class PorterDuffFunctionsTests { + private static readonly ApproximateFloatComparer FloatComparer = new(.000001F); + public static TheoryData NormalBlendFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -33,7 +35,7 @@ public class PorterDuffFunctionsTests Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); Vector256 actual = PorterDuffFunctions.NormalSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual); + Assert.Equal(expected256, actual, FloatComparer); } public static TheoryData MultiplyFunctionData { get; } = new() @@ -51,21 +53,45 @@ public class PorterDuffFunctionsTests VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.MultiplySrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData AddFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2075676f, .2075676f, .2075676f, .37f) } + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(0.24324325f, 0.24324325f, 0.24324325f, .37f) } }; [Theory] [MemberData(nameof(AddFunctionData))] public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); + Vector4 actual = PorterDuffFunctions.AddSrcOver((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.AddSrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData SubtractFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, @@ -81,6 +107,18 @@ public class PorterDuffFunctionsTests VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.SubtractSrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData ScreenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -96,6 +134,18 @@ public class PorterDuffFunctionsTests VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.ScreenSrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData DarkenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -111,6 +161,18 @@ public class PorterDuffFunctionsTests VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.DarkenSrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData LightenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -126,6 +188,18 @@ public class PorterDuffFunctionsTests VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.LightenSrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData OverlayFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -141,6 +215,18 @@ public class PorterDuffFunctionsTests VectorAssert.Equal(expected, actual, 5); } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.OverlaySrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } + public static TheoryData HardLightFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -155,4 +241,16 @@ public class PorterDuffFunctionsTests Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); + Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + + Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); + Vector256 actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount)); + Assert.Equal(expected256, actual, FloatComparer); + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 6d9652d898..e35f36feec 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; using System.Numerics; +using System.Runtime.Intrinsics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests; @@ -14,7 +16,8 @@ internal readonly struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer, + IEqualityComparer> { private readonly float epsilon; @@ -72,4 +75,16 @@ internal readonly struct ApproximateFloatComparer : /// public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); + + public bool Equals(Vector256 x, Vector256 y) + => this.Equals(x.GetElement(0), y.GetElement(0)) + && this.Equals(x.GetElement(1), y.GetElement(1)) + && this.Equals(x.GetElement(2), y.GetElement(2)) + && this.Equals(x.GetElement(3), y.GetElement(3)) + && this.Equals(x.GetElement(4), y.GetElement(4)) + && this.Equals(x.GetElement(5), y.GetElement(5)) + && this.Equals(x.GetElement(6), y.GetElement(6)) + && this.Equals(x.GetElement(7), y.GetElement(7)); + + public int GetHashCode([DisallowNull] Vector256 obj) => obj.GetHashCode(); }