Browse Source

Merge pull request #3150 from SixLabors/js/porter-duff-coverage

Add BlendWithCoverage overloads. Optimize Rgba32 compatible shuffling.
pull/3152/head
James Jackson-South 1 week ago
committed by GitHub
parent
commit
67aa0d341c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 54892
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  2. 444
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  3. 59
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  4. 461
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  5. 16
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs
  6. 16
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs
  7. 16
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs
  8. 45
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude
  9. 221
      tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs

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

File diff suppressed because it is too large

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

@ -401,6 +401,450 @@ var blenders = new []{
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount, ReadOnlySpan<float> coverage)
{
amount = Numerics.Clamp(amount, 0, 1);
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 coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> opacity = Vector512.Create(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// 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> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> opacity = Vector256.Create(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount, ReadOnlySpan<float> coverage)
{
amount = Numerics.Clamp(amount, 0, 1);
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 coverageBase = ref MemoryMarshal.GetReference(coverage);
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);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// 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> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> opacity = Vector256.Create(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage)
{
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);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
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);
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
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);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// 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> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
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, 2);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage)
{
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);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
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);
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// 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> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
}
<#

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

@ -324,6 +324,65 @@ internal static partial class PorterDuffFunctions
return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512<float>.Zero, color));
}
/// <summary>
/// Applies raster coverage to a Porter-Duff composition result.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The Porter-Duff composition result.</param>
/// <param name="coverage">The coverage. Range 0..1.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
Vector4 backdropPremultiplied = Numerics.WithW(backdrop * backdropAlpha, backdropAlpha);
Vector4 sourcePremultiplied = Numerics.WithW(source * sourceAlpha, sourceAlpha);
Vector4 result = backdropPremultiplied + ((sourcePremultiplied - backdropPremultiplied) * coverage);
Numerics.UnPremultiply(ref result);
return result;
}
/// <summary>
/// Applies raster coverage to a Porter-Duff composition result.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The Porter-Duff composition result.</param>
/// <param name="coverage">The coverage. Range 0..1.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> BlendWithCoverage(Vector256<float> backdrop, Vector256<float> source, Vector256<float> coverage)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> backdropPremultiplied = Avx.Blend(backdrop * backdropAlpha, backdropAlpha, BlendAlphaControl);
Vector256<float> sourcePremultiplied = Avx.Blend(source * sourceAlpha, sourceAlpha, BlendAlphaControl);
Vector256<float> result = Vector256_.MultiplyAdd(backdropPremultiplied, sourcePremultiplied - backdropPremultiplied, coverage);
return Numerics.UnPremultiply(result, Avx.Permute(result, ShuffleAlphaControl));
}
/// <summary>
/// Applies raster coverage to a Porter-Duff composition result.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The Porter-Duff composition result.</param>
/// <param name="coverage">The coverage. Range 0..1.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> BlendWithCoverage(Vector512<float> backdrop, Vector512<float> source, Vector512<float> coverage)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> alphaMask = AlphaMask512();
Vector512<float> backdropPremultiplied = Vector512.ConditionalSelect(alphaMask, backdropAlpha, backdrop * backdropAlpha);
Vector512<float> sourcePremultiplied = Vector512.ConditionalSelect(alphaMask, sourceAlpha, source * sourceAlpha);
Vector512<float> result = Vector512_.MultiplyAdd(backdropPremultiplied, sourcePremultiplied - backdropPremultiplied, coverage);
return Numerics.UnPremultiply(result, Vector512_.ShuffleNative(result, ShuffleAlphaControl));
}
/// <summary>
/// Helper function for Overlay and HardLight modes
/// </summary>

461
src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

@ -3,6 +3,7 @@
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.PixelFormats;
@ -167,6 +168,162 @@ public abstract class PixelBlender<TPixel>
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends 2 rows together with per-pixel coverage.
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
public void BlendWithCoverage<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
float amount,
ReadOnlySpan<float> coverage)
where TPixelSrc : unmanaged, IPixel<TPixelSrc>
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 3);
this.BlendWithCoverage(
configuration,
destination,
background,
source,
amount,
coverage,
buffer.Memory.Span[..(maxLength * 3)]);
}
/// <summary>
/// Blends 2 rows together with per-pixel coverage using caller-provided temporary vector scratch.
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 3 rows.</param>
public void BlendWithCoverage<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
float amount,
ReadOnlySpan<float> coverage,
Span<Vector4> workingBuffer)
where TPixelSrc : unmanaged, IPixel<TPixelSrc>
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length));
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
Span<Vector4> sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
PixelOperations<TPixelSrc>.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends a row against a constant source color with per-pixel coverage.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
public void BlendWithCoverage(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
TPixel source,
float amount,
ReadOnlySpan<float> coverage)
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
this.BlendWithCoverage(
configuration,
destination,
background,
source,
amount,
coverage,
buffer.Memory.Span[..(maxLength * 2)]);
}
/// <summary>
/// Blends a row against a constant source color with per-pixel coverage using caller-provided temporary vector scratch.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 2 rows.</param>
public void BlendWithCoverage(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
TPixel source,
float amount,
ReadOnlySpan<float> coverage,
Span<Vector4> workingBuffer)
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length));
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends 2 rows together
/// </summary>
@ -349,6 +506,206 @@ public abstract class PixelBlender<TPixel>
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends 2 rows together with per-pixel coverage.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
public void BlendWithCoverage(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixel> source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage)
=> this.BlendWithCoverage<TPixel>(configuration, destination, background, source, amount, coverage);
/// <summary>
/// Blends 2 rows together with per-pixel coverage using caller-provided temporary vector scratch.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 3 rows.</param>
public void BlendWithCoverage(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixel> source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage,
Span<Vector4> workingBuffer)
=> this.BlendWithCoverage<TPixel>(configuration, destination, background, source, amount, coverage, workingBuffer);
/// <summary>
/// Blends 2 rows together with per-pixel coverage.
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
public void BlendWithCoverage<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage)
where TPixelSrc : unmanaged, IPixel<TPixelSrc>
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 3);
this.BlendWithCoverage(
configuration,
destination,
background,
source,
amount,
coverage,
buffer.Memory.Span[..(maxLength * 3)]);
}
/// <summary>
/// Blends a row against a constant source color with per-pixel coverage.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
public void BlendWithCoverage(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
TPixel source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage)
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
this.BlendWithCoverage(
configuration,
destination,
background,
source,
amount,
coverage,
buffer.Memory.Span[..(maxLength * 2)]);
}
/// <summary>
/// Blends 2 rows together with per-pixel coverage using caller-provided temporary vector scratch.
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 3 rows.</param>
public void BlendWithCoverage<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage,
Span<Vector4> workingBuffer)
where TPixelSrc : unmanaged, IPixel<TPixelSrc>
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length));
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
Span<Vector4> sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
PixelOperations<TPixelSrc>.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends a row against a constant source color with per-pixel coverage using caller-provided temporary vector scratch.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 2 rows.</param>
public void BlendWithCoverage(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
TPixel source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage,
Span<Vector4> workingBuffer)
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length));
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length));
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blend 2 rows together.
/// </summary>
@ -412,4 +769,108 @@ public abstract class PixelBlender<TPixel>
ReadOnlySpan<Vector4> background,
Vector4 source,
ReadOnlySpan<float> amount);
/// <summary>
/// Blend 2 rows together with per-pixel coverage.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
protected virtual void BlendWithCoverageFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
float amount,
ReadOnlySpan<float> coverage)
{
this.BlendFunction(destination, background, source, amount);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F));
}
}
/// <summary>
/// Blend a row against a constant source color with per-pixel coverage.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color vector</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
protected virtual void BlendWithCoverageFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
Vector4 source,
float amount,
ReadOnlySpan<float> coverage)
{
this.BlendFunction(destination, background, source, amount);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F));
}
}
/// <summary>
/// Blend 2 rows together with per-pixel coverage.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
protected virtual void BlendWithCoverageFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage)
{
this.BlendFunction(destination, background, source, amount);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F));
}
}
/// <summary>
/// Blend a row against a constant source color with per-pixel coverage.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color vector</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
/// <param name="coverage">A span with coverage values between 0 and 1.</param>
protected virtual void BlendWithCoverageFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
Vector4 source,
ReadOnlySpan<float> amount,
ReadOnlySpan<float> coverage)
{
this.BlendFunction(destination, background, source, amount);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F));
}
}
}

16
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs

@ -38,6 +38,7 @@ public partial struct Abgr32
source.CopyTo(destination.Slice(0, source.Length));
}
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
@ -45,7 +46,20 @@ public partial struct Abgr32
Span<Abgr32> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale));
Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination));
destination = destination[..sourceVectors.Length];
Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale));
Span<byte> destinationBytes = MemoryMarshal.Cast<Abgr32, byte>(destination);
// The SIMD saturating conversion produces RGBA byte order. Reusing the destination
// buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by
// the generic Rgba-compatible path for non-Rgba32 formats.
SimdUtils.NormalizedFloatToByteSaturate(
MemoryMarshal.Cast<Vector4, float>(sourceVectors),
destinationBytes);
PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes);
}
/// <inheritdoc />

16
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs

@ -38,6 +38,7 @@ public partial struct Argb32
source.CopyTo(destination.Slice(0, source.Length));
}
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
@ -45,7 +46,20 @@ public partial struct Argb32
Span<Argb32> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale));
Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination));
destination = destination[..sourceVectors.Length];
Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale));
Span<byte> destinationBytes = MemoryMarshal.Cast<Argb32, byte>(destination);
// The SIMD saturating conversion produces RGBA byte order. Reusing the destination
// buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by
// the generic Rgba-compatible path for non-Rgba32 formats.
SimdUtils.NormalizedFloatToByteSaturate(
MemoryMarshal.Cast<Vector4, float>(sourceVectors),
destinationBytes);
PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes);
}
/// <inheritdoc />

16
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs

@ -38,6 +38,7 @@ public partial struct Bgra32
source.CopyTo(destination.Slice(0, source.Length));
}
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
@ -45,7 +46,20 @@ public partial struct Bgra32
Span<Bgra32> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale));
Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination));
destination = destination[..sourceVectors.Length];
Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale));
Span<byte> destinationBytes = MemoryMarshal.Cast<Bgra32, byte>(destination);
// The SIMD saturating conversion produces RGBA byte order. Reusing the destination
// buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by
// the generic Rgba-compatible path for non-Rgba32 formats.
SimdUtils.NormalizedFloatToByteSaturate(
MemoryMarshal.Cast<Vector4, float>(sourceVectors),
destinationBytes);
PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes);
}
/// <inheritdoc />

45
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude

@ -152,6 +152,12 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
{
removeTheseModifiers += " | PixelConversionModifiers.Premultiply";
}
if (hasAlpha)
{
GenerateRgba32Compatible32BitVector4ConversionMethods(pixelType, removeTheseModifiers);
return;
}
#>
/// <inheritdoc />
@ -176,6 +182,45 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
<#+
}
void GenerateRgba32Compatible32BitVector4ConversionMethods(string pixelType, string removeTheseModifiers)
{
#>
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
Span<Vector4> sourceVectors,
Span<<#=pixelType#>> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination));
destination = destination[..sourceVectors.Length];
Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(<#=removeTheseModifiers#>));
Span<byte> destinationBytes = MemoryMarshal.Cast<<#=pixelType#>, byte>(destination);
// The SIMD saturating conversion produces RGBA byte order. Reusing the destination
// buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by
// the generic Rgba-compatible path for non-Rgba32 formats.
SimdUtils.NormalizedFloatToByteSaturate(
MemoryMarshal.Cast<Vector4, float>(sourceVectors),
destinationBytes);
PixelConverter.FromRgba32.To<#=pixelType#>(destinationBytes, destinationBytes);
}
/// <inheritdoc />
public override void ToVector4(
Configuration configuration,
ReadOnlySpan<<#=pixelType#>> source,
Span<Vector4> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeTheseModifiers#>));
}
<#+
}
void GenerateAllDefaultConversionMethods(string pixelType)
{
GenerateDefaultSelfConversionMethods(pixelType);

221
tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs

@ -328,6 +328,214 @@ public class PixelBlenderTests
Assert.Equal(source[1], destination[1]);
}
[Fact]
public void BlendWithCoverage_WithConstantSourceAndSingleAmount()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(255, 0, 0),
new(255, 0, 0)
];
Rgba32 source = new(0, 0, 255);
float[] coverage = [0F, .5F, 1F];
blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
Assert.Equal(source, destination[2]);
}
[Fact]
public void BlendWithCoverage_WithConstantSourceSingleAmountAndWorkingBuffer()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(255, 0, 0),
new(255, 0, 0)
];
Rgba32 source = new(0, 0, 255);
float[] coverage = [0F, .5F, 1F];
Vector4[] workingBuffer = new Vector4[destination.Length * 2];
blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage, workingBuffer);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
Assert.Equal(source, destination[2]);
}
[Fact]
public void BlendWithCoverage_WithSourceSpanAndSingleAmount()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(0, 255, 0),
new(0, 0, 255)
];
Rgba32[] source =
[
new(0, 0, 255),
new(255, 0, 0),
new(0, 255, 0)
];
float[] coverage = [0F, .5F, 1F];
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, 1F, coverage);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
Assert.Equal(source[2], destination[2]);
}
[Fact]
public void BlendWithCoverage_WithSourceSpanSingleAmountAndWorkingBuffer()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(0, 255, 0),
new(0, 0, 255)
];
Rgba32[] source =
[
new(0, 0, 255),
new(255, 0, 0),
new(0, 255, 0)
];
float[] coverage = [0F, .5F, 1F];
Vector4[] workingBuffer = new Vector4[destination.Length * 3];
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, 1F, coverage, workingBuffer);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
Assert.Equal(source[2], destination[2]);
}
[Fact]
public void BlendWithCoverage_WithConstantSourceAndAmountSpan()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(255, 0, 0),
new(255, 0, 0)
];
Rgba32 source = new(0, 0, 255);
float[] amount = [1F, 1F, 1F];
float[] coverage = [0F, .5F, 1F];
blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
Assert.Equal(source, destination[2]);
}
[Fact]
public void BlendWithCoverage_WithConstantSourceAmountSpanAndWorkingBuffer()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(255, 0, 0),
new(255, 0, 0)
];
Rgba32 source = new(0, 0, 255);
float[] amount = [1F, 1F, 1F];
float[] coverage = [0F, .5F, 1F];
Vector4[] workingBuffer = new Vector4[destination.Length * 2];
blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage, workingBuffer);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
Assert.Equal(source, destination[2]);
}
[Fact]
public void BlendWithCoverage_WithSourceSpanAndAmountSpan()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(0, 255, 0),
new(0, 0, 255)
];
Rgba32[] source =
[
new(0, 0, 255),
new(255, 0, 0),
new(0, 255, 0)
];
float[] amount = [1F, 1F, 1F];
float[] coverage = [0F, .5F, 1F];
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, amount, coverage);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
Assert.Equal(source[2], destination[2]);
}
[Fact]
public void BlendWithCoverage_WithSourceSpanAmountSpanAndWorkingBuffer()
{
PixelBlender<Rgba32> blender = new DefaultPixelBlenders<Rgba32>.NormalSrc();
Rgba32[] destination = new Rgba32[3];
Rgba32[] background =
[
new(255, 0, 0),
new(0, 255, 0),
new(0, 0, 255)
];
Rgba32[] source =
[
new(0, 0, 255),
new(255, 0, 0),
new(0, 255, 0)
];
float[] amount = [1F, 1F, 1F];
float[] coverage = [0F, .5F, 1F];
Vector4[] workingBuffer = new Vector4[destination.Length * 3];
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, amount, coverage, workingBuffer);
Assert.Equal(background[0], destination[0]);
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
Assert.Equal(source[2], destination[2]);
}
public static TheoryData<Rgba32, Rgba32, float, PixelColorBlendingMode, Rgba32> ColorBlendingExpectedResults = new()
{
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Normal, Color.MidnightBlue.ToPixel<Rgba32>() },
@ -421,6 +629,7 @@ public class PixelBlenderTests
Rgba32 background = Color.MistyRose.ToPixel<Rgba32>();
Rgba32 source = Color.MidnightBlue.ToPixel<Rgba32>();
float[] amount = [1F, 1F, 1F, 1F];
float[] coverage = [1F, 1F, 1F, 1F];
Rgba32 expected = blender.Blend(background, source, 1F);
@ -441,5 +650,17 @@ public class PixelBlenderTests
blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, coverage, sourceSpanBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, 1F, coverage, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
}
}

Loading…
Cancel
Save