Browse Source

Merge branch 'main' into bp/openExr

pull/3096/head
Brian Popow 1 month ago
committed by GitHub
parent
commit
2abbedcd64
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 16
      src/ImageSharp/Common/Helpers/Numerics.cs
  2. 45
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  3. 22
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  4. 15
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  5. 17056
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  6. 156
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  7. 1568
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  8. 172
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  9. 299
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  10. 23
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs
  11. 2
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
  12. 152
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs
  13. 108
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs
  14. 18
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  15. 2
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

16
src/ImageSharp/Common/Helpers/Numerics.cs

@ -643,6 +643,20 @@ internal static class Numerics
return Avx.Blend(result, alpha, BlendAlphaControl); return Avx.Blend(result, alpha, BlendAlphaControl);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> UnPremultiply(Vector512<float> source, Vector512<float> alpha)
{
// Check if alpha is zero to avoid division by zero
Vector512<float> zeroMask = Vector512.Equals(alpha, Vector512<float>.Zero);
// Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value
Vector512<float> result = Vector512.ConditionalSelect(zeroMask, source, source / alpha);
// Blend the result with the alpha vector to ensure that the alpha component is unchanged
Vector512<float> alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle();
return Vector512.ConditionalSelect(alphaMask, alpha, result);
}
/// <summary> /// <summary>
/// Permutes the given vector return a new instance with all the values set to <see cref="Vector4.W"/>. /// Permutes the given vector return a new instance with all the values set to <see cref="Vector4.W"/>.
/// </summary> /// </summary>
@ -690,7 +704,7 @@ internal static class Numerics
/// </summary> /// </summary>
/// <param name="vectors">The span of vectors</param> /// <param name="vectors">The span of vectors</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void CubePowOnXYZ(Span<Vector4> vectors) public static void CubePowOnXYZ(Span<Vector4> vectors)
{ {
ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
ref Vector4 endRef = ref Unsafe.Add(ref baseRef, (uint)vectors.Length); ref Vector4 endRef = ref Unsafe.Add(ref baseRef, (uint)vectors.Length);

45
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -601,51 +601,6 @@ internal static partial class SimdUtils
} }
} }
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.AlwaysInline)]
public static Vector256<float> MultiplyAdd(
Vector256<float> va,
Vector256<float> vm0,
Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(vm1, vm0, va);
}
return va + (vm0 * vm1);
}
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector256{Single}"/>.
/// </summary>
/// <remarks>ret = c - (a * b)</remarks>
/// <param name="a">The first vector to multiply.</param>
/// <param name="b">The second vector to multiply.</param>
/// <param name="c">The vector to add negated to the intermediate result.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplyAddNegated(
Vector256<float> a,
Vector256<float> b,
Vector256<float> c)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAddNegated(a, b, c);
}
return Avx.Subtract(c, Avx.Multiply(a, b));
}
/// <summary> /// <summary>
/// Blend packed 8-bit integers from <paramref name="left"/> and <paramref name="right"/> using <paramref name="mask"/>. /// Blend packed 8-bit integers from <paramref name="left"/> and <paramref name="right"/> using <paramref name="mask"/>.
/// The high bit of each corresponding <paramref name="mask"/> byte determines the selection. /// The high bit of each corresponding <paramref name="mask"/> byte determines the selection.

22
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -115,6 +115,28 @@ internal static class Vector256_
return va + (vm0 * vm1); return va + (vm0 * vm1);
} }
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector256{Single}"/>.
/// </summary>
/// <remarks>ret = va - (vm0 * vm1)</remarks>
/// <param name="va">The vector to add to the negated intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplyAddNegated(
Vector256<float> va,
Vector256<float> vm0,
Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAddNegated(vm0, vm1, va);
}
return va - (vm0 * vm1);
}
/// <summary> /// <summary>
/// Performs a multiplication and a subtraction of the <see cref="Vector256{Single}"/>. /// Performs a multiplication and a subtraction of the <see cref="Vector256{Single}"/>.
/// </summary> /// </summary>

15
src/ImageSharp/Common/Helpers/Vector512Utilities.cs

@ -87,6 +87,21 @@ internal static class Vector512_
Vector512<float> vm1) Vector512<float> vm1)
=> Avx512F.FusedMultiplyAdd(vm0, vm1, va); => Avx512F.FusedMultiplyAdd(vm0, vm1, va);
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector512{Single}"/>.
/// </summary>
/// <remarks>ret = va - (vm0 * vm1)</remarks>
/// <param name="va">The vector to add to the negated intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector512{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> MultiplyAddNegated(
Vector512<float> va,
Vector512<float> vm0,
Vector512<float> vm1)
=> Avx512F.FusedMultiplyAddNegated(vm0, vm1, va);
/// <summary> /// <summary>
/// Restricts a vector between a minimum and a maximum value. /// Restricts a vector between a minimum and a maximum value.
/// </summary> /// </summary>

17056
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

File diff suppressed because it is too large

156
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -89,7 +89,34 @@ var blenders = new []{
{ {
amount = Numerics.Clamp(amount, 0, 1); amount = Numerics.Clamp(amount, 0, 1);
if (Avx2.IsSupported && destination.Length >= 2) if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{ {
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> // Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
@ -128,7 +155,37 @@ var blenders = new []{
{ {
amount = Numerics.Clamp(amount, 0, 1); amount = Numerics.Clamp(amount, 0, 1);
if (Avx2.IsSupported && destination.Length >= 2) if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{ {
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> // Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
@ -164,7 +221,51 @@ var blenders = new []{
/// <inheritdoc /> /// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount) protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{ {
if (Avx2.IsSupported && destination.Length >= 2) if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{ {
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> // Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
@ -211,7 +312,54 @@ var blenders = new []{
/// <inheritdoc /> /// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount) protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
{ {
if (Avx2.IsSupported && destination.Length >= 2) if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{ {
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float> // Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination)); ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));

1568
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs

File diff suppressed because it is too large

172
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt

@ -47,7 +47,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Src(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>Src(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); => Avx.Blend(source, source * opacity, BlendAlphaControl);
/// <summary>
/// Returns the result of the "<#=blender#>Src compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Src(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation. /// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
@ -74,7 +85,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>SrcAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{ {
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); source = Avx.Blend(source, source * opacity, BlendAlphaControl);
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcAtop(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Atop(backdrop, source, <#=blender#>(backdrop, source)); return Atop(backdrop, source, <#=blender#>(backdrop, source));
} }
@ -104,7 +130,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>SrcOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{ {
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); source = Avx.Blend(source, source * opacity, BlendAlphaControl);
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcOver(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Over(backdrop, source, <#=blender#>(backdrop, source)); return Over(backdrop, source, <#=blender#>(backdrop, source));
} }
@ -133,7 +174,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>SrcIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>SrcIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcIn(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation. /// Returns the result of the "<#=blender#>SrcOut" compositing equation.
@ -159,7 +211,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>SrcOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcOut(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation. /// Returns the result of the "<#=blender#>Dest" compositing equation.
@ -187,6 +250,19 @@ internal static partial class PorterDuffFunctions
return backdrop; return backdrop;
} }
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Dest(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
return backdrop;
}
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation. /// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary> /// </summary>
@ -212,7 +288,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>DestAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{ {
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); source = Avx.Blend(source, source * opacity, BlendAlphaControl);
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestAtop(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Atop(source, backdrop, <#=blender#>(source, backdrop)); return Atop(source, backdrop, <#=blender#>(source, backdrop));
} }
@ -242,7 +333,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>DestOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{ {
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); source = Avx.Blend(source, source * opacity, BlendAlphaControl);
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
/// <summary>
/// Returns the result of the "<#=blender#>DestOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestOver(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Over(source, backdrop, <#=blender#>(source, backdrop)); return Over(source, backdrop, <#=blender#>(source, backdrop));
} }
@ -271,7 +377,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>DestIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>DestIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestIn(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop);
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation. /// Returns the result of the "<#=blender#>DestOut" compositing equation.
@ -297,7 +414,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>DestOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestOut(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop);
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation. /// Returns the result of the "<#=blender#>Xor" compositing equation.
@ -323,7 +451,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Xor(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>Xor(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Xor(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
/// <summary> /// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation. /// Returns the result of the "<#=blender#>Clear" compositing equation.
@ -349,7 +488,18 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Clear(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity) public static Vector256<float> <#=blender#>Clear(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Clear(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
<#} #> <#} #>

299
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -5,6 +5,7 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -44,6 +45,16 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> Normal(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Normal(Vector256<float> backdrop, Vector256<float> source)
=> source; => source;
/// <summary>
/// Returns the result of the "Normal" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Normal(Vector512<float> backdrop, Vector512<float> source)
=> source;
/// <summary> /// <summary>
/// Returns the result of the "Multiply" compositing equation. /// Returns the result of the "Multiply" compositing equation.
/// </summary> /// </summary>
@ -62,7 +73,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Multiply(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Multiply(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Multiply(backdrop, source); => backdrop * source;
/// <summary>
/// Returns the result of the "Multiply" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Multiply(Vector512<float> backdrop, Vector512<float> source)
=> backdrop * source;
/// <summary> /// <summary>
/// Returns the result of the "Add" compositing equation. /// Returns the result of the "Add" compositing equation.
@ -82,7 +103,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Add(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Add(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Min(Vector256.Create(1F), Avx.Add(backdrop, source)); => Vector256.Min(Vector256.Create(1F), backdrop + source);
/// <summary>
/// Returns the result of the "Add" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Add(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Min(Vector512.Create(1F), backdrop + source);
/// <summary> /// <summary>
/// Returns the result of the "Subtract" compositing equation. /// Returns the result of the "Subtract" compositing equation.
@ -102,7 +133,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Subtract(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Subtract(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Max(Vector256<float>.Zero, Avx.Subtract(backdrop, source)); => Vector256.Max(Vector256<float>.Zero, backdrop - source);
/// <summary>
/// Returns the result of the "Subtract" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Subtract(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Max(Vector512<float>.Zero, backdrop - source);
/// <summary> /// <summary>
/// Returns the result of the "Screen" compositing equation. /// Returns the result of the "Screen" compositing equation.
@ -124,7 +165,20 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source)
{ {
Vector256<float> vOne = Vector256.Create(1F); Vector256<float> vOne = Vector256.Create(1F);
return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne); return Vector256_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source);
}
/// <summary>
/// Returns the result of the "Screen" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Screen(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> vOne = Vector512.Create(1F);
return Vector512_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source);
} }
/// <summary> /// <summary>
@ -145,7 +199,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Darken(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Darken(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Min(backdrop, source); => Vector256.Min(backdrop, source);
/// <summary>
/// Returns the result of the "Darken" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Darken(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Min(backdrop, source);
/// <summary> /// <summary>
/// Returns the result of the "Lighten" compositing equation. /// Returns the result of the "Lighten" compositing equation.
@ -164,7 +228,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector256{Single}"/>.</returns> /// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lighten(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Lighten(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Max(backdrop, source); => Vector256.Max(backdrop, source);
/// <summary>
/// Returns the result of the "Lighten" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Lighten(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Max(backdrop, source);
/// <summary> /// <summary>
/// Returns the result of the "Overlay" compositing equation. /// Returns the result of the "Overlay" compositing equation.
@ -192,7 +266,20 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> Overlay(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> Overlay(Vector256<float> backdrop, Vector256<float> source)
{ {
Vector256<float> color = OverlayValueFunction(backdrop, source); Vector256<float> color = OverlayValueFunction(backdrop, source);
return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl)); return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// Returns the result of the "Overlay" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Overlay(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> color = OverlayValueFunction(backdrop, source);
return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512<float>.Zero, color));
} }
/// <summary> /// <summary>
@ -221,7 +308,20 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> HardLight(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> HardLight(Vector256<float> backdrop, Vector256<float> source)
{ {
Vector256<float> color = OverlayValueFunction(source, backdrop); Vector256<float> color = OverlayValueFunction(source, backdrop);
return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl)); return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// Returns the result of the "HardLight" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> HardLight(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> color = OverlayValueFunction(source, backdrop);
return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512<float>.Zero, color));
} }
/// <summary> /// <summary>
@ -244,14 +344,32 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> OverlayValueFunction(Vector256<float> backdrop, Vector256<float> source) public static Vector256<float> OverlayValueFunction(Vector256<float> backdrop, Vector256<float> source)
{ {
Vector256<float> vOne = Vector256.Create(1F); Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> left = Avx.Multiply(Avx.Add(backdrop, backdrop), source); Vector256<float> left = (backdrop + backdrop) * source;
Vector256<float> vOneMinusSource = Avx.Subtract(vOne, source); Vector256<float> vOneMinusSource = Avx.Subtract(vOne, source);
Vector256<float> right = SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Add(vOneMinusSource, vOneMinusSource), Avx.Subtract(vOne, backdrop), vOne); Vector256<float> right = Vector256_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop);
Vector256<float> cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F)); Vector256<float> cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F));
return Avx.BlendVariable(left, right, cmp); return Avx.BlendVariable(left, right, cmp);
} }
/// <summary>
/// Helper function for Overlay and HardLight modes
/// </summary>
/// <param name="backdrop">Backdrop color element</param>
/// <param name="source">Source color element</param>
/// <returns>Overlay value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> OverlayValueFunction(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> vOne = Vector512.Create(1F);
Vector512<float> left = (backdrop + backdrop) * source;
Vector512<float> vOneMinusSource = vOne - source;
Vector512<float> right = Vector512_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop);
Vector512<float> cmp = Avx512F.CompareGreaterThan(backdrop, Vector512.Create(.5F));
return Vector512.ConditionalSelect(cmp, right, left);
}
/// <summary> /// <summary>
/// Returns the result of the "Over" compositing equation. /// Returns the result of the "Over" compositing equation.
/// </summary> /// </summary>
@ -295,17 +413,47 @@ internal static partial class PorterDuffFunctions
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl); Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> dW = Avx.Permute(destination, ShuffleAlphaControl); Vector256<float> dW = Avx.Permute(destination, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, dW); Vector256<float> blendW = sW * dW;
Vector256<float> dstW = Avx.Subtract(dW, blendW); Vector256<float> dstW = dW - blendW;
Vector256<float> srcW = Avx.Subtract(sW, blendW); Vector256<float> srcW = sW - blendW;
// calculate final alpha
Vector256<float> alpha = dstW + sW;
// calculate final color
Vector256<float> color = destination * dstW;
color = Vector256_.MultiplyAdd(color, source, srcW);
color = Vector256_.MultiplyAdd(color, blend, blendW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <summary>
/// Returns the result of the "Over" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Over(Vector512<float> destination, Vector512<float> source, Vector512<float> blend)
{
// calculate weights
Vector512<float> sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> blendW = sW * dW;
Vector512<float> dstW = dW - blendW;
Vector512<float> srcW = sW - blendW;
// calculate final alpha // calculate final alpha
Vector256<float> alpha = Avx.Add(dstW, sW); Vector512<float> alpha = dstW + sW;
// calculate final color // calculate final color
Vector256<float> color = Avx.Multiply(destination, dstW); Vector512<float> color = destination * dstW;
color = SimdUtils.HwIntrinsics.MultiplyAdd(color, source, srcW); color = Vector512_.MultiplyAdd(color, source, srcW);
color = SimdUtils.HwIntrinsics.MultiplyAdd(color, blend, blendW); color = Vector512_.MultiplyAdd(color, blend, blendW);
// unpremultiply // unpremultiply
return Numerics.UnPremultiply(color, alpha); return Numerics.UnPremultiply(color, alpha);
@ -354,11 +502,36 @@ internal static partial class PorterDuffFunctions
// calculate weights // calculate weights
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl); Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, alpha); Vector256<float> blendW = sW * alpha;
Vector256<float> dstW = Avx.Subtract(alpha, blendW); Vector256<float> dstW = alpha - blendW;
// calculate final color // calculate final color
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW); Vector256<float> color = Vector256_.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <summary>
/// Returns the result of the "Atop" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Atop(Vector512<float> destination, Vector512<float> source, Vector512<float> blend)
{
// calculate final alpha
Vector512<float> alpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
// calculate weights
Vector512<float> sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> blendW = sW * alpha;
Vector512<float> dstW = alpha - blendW;
// calculate final color
Vector512<float> color = Vector512_.MultiplyAdd(blend * blendW, destination, dstW);
// unpremultiply // unpremultiply
return Numerics.UnPremultiply(color, alpha); return Numerics.UnPremultiply(color, alpha);
@ -392,10 +565,29 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> In(Vector256<float> destination, Vector256<float> source) public static Vector256<float> In(Vector256<float> destination, Vector256<float> source)
{ {
// calculate alpha // calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl); Vector256<float> alpha = Avx.Permute(source * destination, ShuffleAlphaControl);
// premultiply // premultiply
Vector256<float> color = Avx.Multiply(source, alpha); Vector256<float> color = source * alpha;
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <summary>
/// Returns the result of the "In" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> In(Vector512<float> destination, Vector512<float> source)
{
// calculate alpha
Vector512<float> alpha = Vector512_.ShuffleNative(source * destination, ShuffleAlphaControl);
// premultiply
Vector512<float> color = source * alpha;
// unpremultiply // unpremultiply
return Numerics.UnPremultiply(color, alpha); return Numerics.UnPremultiply(color, alpha);
@ -429,10 +621,29 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> Out(Vector256<float> destination, Vector256<float> source) public static Vector256<float> Out(Vector256<float> destination, Vector256<float> source)
{ {
// calculate alpha // calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, Avx.Subtract(Vector256.Create(1F), destination)), ShuffleAlphaControl); Vector256<float> alpha = Avx.Permute(source * (Vector256.Create(1F) - destination), ShuffleAlphaControl);
// premultiply
Vector256<float> color = source * alpha;
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <summary>
/// Returns the result of the "Out" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Out(Vector512<float> destination, Vector512<float> source)
{
// calculate alpha
Vector512<float> alpha = Vector512_.ShuffleNative(source * (Vector512.Create(1F) - destination), ShuffleAlphaControl);
// premultiply // premultiply
Vector256<float> color = Avx.Multiply(source, alpha); Vector512<float> color = source * alpha;
// unpremultiply // unpremultiply
return Numerics.UnPremultiply(color, alpha); return Numerics.UnPremultiply(color, alpha);
@ -475,12 +686,37 @@ internal static partial class PorterDuffFunctions
Vector256<float> dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl); Vector256<float> dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl);
Vector256<float> vOne = Vector256.Create(1F); Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> srcW = Avx.Subtract(vOne, dW); Vector256<float> srcW = vOne - dW;
Vector256<float> dstW = Avx.Subtract(vOne, sW); Vector256<float> dstW = vOne - sW;
// calculate alpha // calculate alpha
Vector256<float> alpha = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW); Vector256<float> alpha = Vector256_.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW);
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW); Vector256<float> color = Vector256_.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <summary>
/// Returns the result of the "XOr" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Xor(Vector512<float> destination, Vector512<float> source)
{
// calculate weights
Vector512<float> sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> vOne = Vector512.Create(1F);
Vector512<float> srcW = vOne - dW;
Vector512<float> dstW = vOne - sW;
// calculate alpha
Vector512<float> alpha = Vector512_.MultiplyAdd(dW * dstW, sW, srcW);
Vector512<float> color = Vector512_.MultiplyAdd((dW * destination) * dstW, sW * source, srcW);
// unpremultiply // unpremultiply
return Numerics.UnPremultiply(color, alpha); return Numerics.UnPremultiply(color, alpha);
@ -491,4 +727,11 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Clear(Vector256<float> backdrop, Vector256<float> source) => Vector256<float>.Zero; private static Vector256<float> Clear(Vector256<float> backdrop, Vector256<float> source) => Vector256<float>.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> Clear(Vector512<float> backdrop, Vector512<float> source) => Vector512<float>.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> AlphaMask512()
=> Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle();
} }

23
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs

@ -18,8 +18,8 @@ public class PorterDuffBulkVsSingleVector
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
this.backdrop = new Vector4[8 * 20]; this.backdrop = new Vector4[8 * 40];
this.source = new Vector4[8 * 20]; this.source = new Vector4[8 * 40];
FillRandom(this.backdrop); FillRandom(this.backdrop);
FillRandom(this.source); FillRandom(this.source);
@ -49,7 +49,7 @@ public class PorterDuffBulkVsSingleVector
return result; return result;
} }
[Benchmark(Description = "Avx")] [Benchmark(Description = "Avx2")]
public Vector256<float> OverlayValueFunction_Avx() public Vector256<float> OverlayValueFunction_Avx()
{ {
ref Vector256<float> backdrop = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop)); ref Vector256<float> backdrop = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop));
@ -65,4 +65,21 @@ public class PorterDuffBulkVsSingleVector
return result; return result;
} }
[Benchmark(Description = "Avx512")]
public Vector512<float> OverlayValueFunction_Avx512()
{
ref Vector512<float> backdrop = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop));
ref Vector512<float> source = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference<Vector4>(this.source));
Vector512<float> result = default;
Vector512<float> opacity = Vector512.Create(.5F);
int count = this.backdrop.Length / 4;
for (nuint i = 0; i < (uint)count; i++)
{
result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity);
}
return result;
}
} }

2
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs

@ -59,7 +59,7 @@ public class PorterDuffCompositorTests
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX,
provider, provider,
mode.ToString()); mode.ToString());
} }

152
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs

@ -4,7 +4,6 @@
using System.Numerics; using System.Numerics;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
using Castle.Components.DictionaryAdapter;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
@ -45,6 +44,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(NormalBlendFunctionData))]
public void NormalBlendFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.NormalSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData { 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -77,6 +92,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(MultiplyFunctionData))]
public void MultiplyFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.MultiplySrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> AddFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -109,6 +140,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(AddFunctionData))]
public void AddFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.AddSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubtractFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubtractFunctionData { get; } = new()
{ {
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) },
@ -141,6 +188,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(SubtractFunctionData))]
public void SubtractFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.SubtractSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData { 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -173,6 +236,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(ScreenFunctionData))]
public void ScreenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.ScreenSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData { 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -205,6 +284,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(DarkenFunctionData))]
public void DarkenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.DarkenSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData { 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -237,6 +332,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(LightenFunctionData))]
public void LightenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.LightenSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData { 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -269,6 +380,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(OverlayFunctionData))]
public void OverlayFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.OverlaySrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData { get; } = new() public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData { 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(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
@ -300,4 +427,27 @@ public class PorterDuffFunctionsTests
Vector256<float> actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount)); Vector256<float> actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer); Assert.Equal(expected256, actual, FloatComparer);
} }
[Theory]
[MemberData(nameof(HardLightFunctionData))]
public void HardLightFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.HardLightSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
private static Vector512<float> CreateVector512(TestVector4 vector)
=> Vector512.Create(
vector.X, vector.Y, vector.Z, vector.W,
vector.X, vector.Y, vector.Z, vector.W,
vector.X, vector.Y, vector.Z, vector.W,
vector.X, vector.Y, vector.Z, vector.W);
} }

108
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs

@ -9,12 +9,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
public class PorterDuffFunctionsTestsTPixel public class PorterDuffFunctionsTestsTPixel
{ {
private const int BulkBlendCount = 4;
private static Span<T> AsSpan<T>(T value) private static Span<T> AsSpan<T>(T value)
where T : struct where T : struct
{ {
return new Span<T>(new[] { value }); return new Span<T>(new[] { value });
} }
private static T[] CreateFilledArray<T>(T value)
{
T[] values = new T[BulkBlendCount];
values.AsSpan().Fill(value);
return values;
}
public static TheoryData<object, object, float, object> NormalBlendFunctionData = new() public static TheoryData<object, object, float, object> NormalBlendFunctionData = new()
{ {
{ new TestPixel<Rgba32>(1, 1, 1, 1), new TestPixel<Rgba32>(1, 1, 1, 1), 1, new TestPixel<Rgba32>(1, 1, 1, 1) }, { new TestPixel<Rgba32>(1, 1, 1, 1), new TestPixel<Rgba32>(1, 1, 1, 1), 1, new TestPixel<Rgba32>(1, 1, 1, 1) },
@ -46,9 +55,14 @@ public class PorterDuffFunctionsTestsTPixel
public void NormalBlendFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void NormalBlendFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> MultiplyFunctionData = new() public static TheoryData<object, object, float, object> MultiplyFunctionData = new()
@ -86,9 +100,14 @@ public class PorterDuffFunctionsTestsTPixel
public void MultiplyFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void MultiplyFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> AddFunctionData = new() public static TheoryData<object, object, float, object> AddFunctionData = new()
@ -136,9 +155,14 @@ public class PorterDuffFunctionsTestsTPixel
public void AddFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void AddFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> SubtractFunctionData = new() public static TheoryData<object, object, float, object> SubtractFunctionData = new()
@ -176,9 +200,14 @@ public class PorterDuffFunctionsTestsTPixel
public void SubtractFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void SubtractFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> ScreenFunctionData = new() public static TheoryData<object, object, float, object> ScreenFunctionData = new()
@ -216,9 +245,14 @@ public class PorterDuffFunctionsTestsTPixel
public void ScreenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void ScreenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> DarkenFunctionData = new() public static TheoryData<object, object, float, object> DarkenFunctionData = new()
@ -256,9 +290,14 @@ public class PorterDuffFunctionsTestsTPixel
public void DarkenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void DarkenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> LightenFunctionData = new() public static TheoryData<object, object, float, object> LightenFunctionData = new()
@ -296,9 +335,14 @@ public class PorterDuffFunctionsTestsTPixel
public void LightenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void LightenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> OverlayFunctionData = new() public static TheoryData<object, object, float, object> OverlayFunctionData = new()
@ -336,9 +380,14 @@ public class PorterDuffFunctionsTestsTPixel
public void OverlayFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void OverlayFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
public static TheoryData<object, object, float, object> HardLightFunctionData = new() public static TheoryData<object, object, float, object> HardLightFunctionData = new()
@ -376,8 +425,13 @@ public class PorterDuffFunctionsTestsTPixel
public void HardLightFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) public void HardLightFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> dest = new(new TPixel[1]); TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
} }
} }

18
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -15,7 +15,8 @@ internal readonly struct ApproximateFloatComparer :
IEqualityComparer<Vector2>, IEqualityComparer<Vector2>,
IEqualityComparer<Vector4>, IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>, IEqualityComparer<ColorMatrix>,
IEqualityComparer<Vector256<float>> IEqualityComparer<Vector256<float>>,
IEqualityComparer<Vector512<float>>
{ {
private readonly float epsilon; private readonly float epsilon;
@ -78,4 +79,19 @@ internal readonly struct ApproximateFloatComparer :
&& this.Equals(x.GetElement(7), y.GetElement(7)); && this.Equals(x.GetElement(7), y.GetElement(7));
public int GetHashCode([DisallowNull] Vector256<float> obj) => obj.GetHashCode(); public int GetHashCode([DisallowNull] Vector256<float> obj) => obj.GetHashCode();
public bool Equals(Vector512<float> x, Vector512<float> y)
{
for (int i = 0; i < Vector512<float>.Count; i++)
{
if (!this.Equals(x.GetElement(i), y.GetElement(i)))
{
return false;
}
}
return true;
}
public int GetHashCode([DisallowNull] Vector512<float> obj) => obj.GetHashCode();
} }

2
tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

@ -455,6 +455,7 @@ public enum HwIntrinsics : long
DisableVAES = 1L << 17, DisableVAES = 1L << 17,
DisableWAITPKG = 1L << 18, DisableWAITPKG = 1L << 18,
DisableX86Serialize = 1 << 19, DisableX86Serialize = 1 << 19,
// Arm64 // Arm64
DisableArm64Aes = 1L << 20, DisableArm64Aes = 1L << 20,
DisableArm64Atomics = 1L << 21, DisableArm64Atomics = 1L << 21,
@ -466,6 +467,7 @@ public enum HwIntrinsics : long
DisableArm64Sha256 = 1L << 27, DisableArm64Sha256 = 1L << 27,
DisableArm64Sve = 1L << 28, DisableArm64Sve = 1L << 28,
DisableArm64Sve2 = 1L << 29, DisableArm64Sve2 = 1L << 29,
// RISC-V64 // RISC-V64
DisableRiscV64Zba = 1L << 30, DisableRiscV64Zba = 1L << 30,
DisableRiscV64Zbb = 1L << 31, DisableRiscV64Zbb = 1L << 31,

Loading…
Cancel
Save