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();
}