Browse Source

Add associated-alpha pixel formats

Introduce Rgba32P, Bgra32P, Argb32P, Abgr32P, NormalizedByte4P, and HalfVector4P.
Add representation-aware scalar and bulk conversions, optimized pixel operations, and associated-alpha blending across all Porter-Duff modes. Include comprehensive conversion, layout, blending, and performance coverage.
js/premultiplied-pixel-formats
James Jackson-South 3 days ago
parent
commit
c4683902a4
  1. 12
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 139
      src/ImageSharp/Color/Color.cs
  3. 32
      src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs
  4. 60
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  5. 25
      src/ImageSharp/Common/Helpers/Vector128Utilities.cs
  6. 20
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  7. 13
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  8. 18
      src/ImageSharp/ImageSharp.csproj
  9. 258
      src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs
  10. 84152
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs
  11. 847
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt
  12. 168
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs
  13. 46
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs
  14. 5035
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs
  15. 138
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt
  16. 546
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs
  17. 4
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  18. 82
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  19. 241
      src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs
  20. 241
      src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs
  21. 241
      src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs
  22. 243
      src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs
  23. 276
      src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs
  24. 66
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs
  25. 66
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs
  26. 66
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs
  27. 3
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs
  28. 3
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs
  29. 14
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude
  30. 57
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs
  31. 62
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs
  32. 66
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs
  33. 5
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs
  34. 241
      src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs
  35. 65
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  36. 595
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs
  37. 11
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
  38. 79
      tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs
  39. 64
      tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs
  40. 29
      tests/ImageSharp.Benchmarks/Config.cs
  41. 101
      tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs
  42. 14
      tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs
  43. 59
      tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs
  44. 22
      tests/ImageSharp.Tests/Color/ColorTests.cs
  45. 43
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  46. 667
      tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs
  47. 173
      tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs
  48. 8
      tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs
  49. 93
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs
  50. 16
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude
  51. 135
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  52. 4
      tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png
  53. 4
      tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png
  54. 4
      tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png
  55. 4
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png
  56. 4
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png
  57. 4
      tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png

12
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -81,10 +81,13 @@ internal static class AotCompilerTools
Seed<A8>();
Seed<Argb32>();
Seed<Argb32P>();
Seed<Abgr32>();
Seed<Abgr32P>();
Seed<Bgr24>();
Seed<Bgr565>();
Seed<Bgra32>();
Seed<Bgra32P>();
Seed<Bgra4444>();
Seed<Bgra5551>();
Seed<Byte4>();
@ -95,8 +98,10 @@ internal static class AotCompilerTools
Seed<HalfSingle>();
Seed<HalfVector2>();
Seed<HalfVector4>();
Seed<HalfVector4P>();
Seed<NormalizedByte2>();
Seed<NormalizedByte4>();
Seed<NormalizedByte4P>();
Seed<NormalizedShort2>();
Seed<NormalizedShort4>();
Seed<Rg32>();
@ -104,6 +109,7 @@ internal static class AotCompilerTools
Seed<Rgb48>();
Seed<Rgba1010102>();
Seed<Rgba32>();
Seed<Rgba32P>();
Seed<Rgba64>();
Seed<RgbaVector>();
Seed<Short2>();
@ -158,10 +164,13 @@ internal static class AotCompilerTools
Image<TPixel> img = default;
img.CloneAs<A8>(default);
img.CloneAs<Argb32>(default);
img.CloneAs<Argb32P>(default);
img.CloneAs<Abgr32>(default);
img.CloneAs<Abgr32P>(default);
img.CloneAs<Bgr24>(default);
img.CloneAs<Bgr565>(default);
img.CloneAs<Bgra32>(default);
img.CloneAs<Bgra32P>(default);
img.CloneAs<Bgra4444>(default);
img.CloneAs<Bgra5551>(default);
img.CloneAs<Byte4>(default);
@ -172,8 +181,10 @@ internal static class AotCompilerTools
img.CloneAs<HalfSingle>(default);
img.CloneAs<HalfVector2>(default);
img.CloneAs<HalfVector4>(default);
img.CloneAs<HalfVector4P>(default);
img.CloneAs<NormalizedByte2>(default);
img.CloneAs<NormalizedByte4>(default);
img.CloneAs<NormalizedByte4P>(default);
img.CloneAs<NormalizedShort2>(default);
img.CloneAs<NormalizedShort4>(default);
img.CloneAs<Rg32>(default);
@ -181,6 +192,7 @@ internal static class AotCompilerTools
img.CloneAs<Rgb48>(default);
img.CloneAs<Rgba1010102>(default);
img.CloneAs<Rgba32>(default);
img.CloneAs<Rgba32P>(default);
img.CloneAs<Rgba64>(default);
img.CloneAs<RgbaVector>(default);
img.CloneAs<Short2>(default);

139
src/ImageSharp/Color/Color.cs

@ -20,30 +20,67 @@ namespace SixLabors.ImageSharp;
public readonly partial struct Color : IEquatable<Color>
{
private readonly Vector4 data;
private readonly Vector4 unassociatedData;
private readonly IPixel? boxedHighPrecisionPixel;
private readonly bool isAssociated;
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
/// <param name="alphaRepresentation">The alpha representation of <paramref name="vector"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Color(Vector4 vector)
private Color(Vector4 vector, PixelAlphaRepresentation alphaRepresentation)
{
Vector4 clamped = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
Vector4 unassociated = clamped;
if (alphaRepresentation == PixelAlphaRepresentation.Associated)
{
Numerics.UnPremultiply(ref unassociated);
}
this.data = clamped;
this.unassociatedData = unassociated;
this.boxedHighPrecisionPixel = null;
this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
/// <param name="unassociatedVector">The unassociated representation of <paramref name="vector"/>.</param>
/// <param name="alphaRepresentation">The alpha representation of <paramref name="vector"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Color(Vector4 vector, Vector4 unassociatedVector, PixelAlphaRepresentation alphaRepresentation)
{
this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.unassociatedData = Numerics.Clamp(unassociatedVector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = null;
this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The pixel containing color information.</param>
/// <param name="alphaRepresentation">The alpha representation of <paramref name="pixel"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Color(IPixel pixel)
private Color(IPixel pixel, PixelAlphaRepresentation alphaRepresentation)
{
this.boxedHighPrecisionPixel = pixel;
this.data = default;
this.unassociatedData = default;
this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated;
}
/// <summary>
/// Gets the alpha representation used by this color's scaled vector.
/// </summary>
public PixelAlphaRepresentation AlphaRepresentation
=> this.isAssociated ? PixelAlphaRepresentation.Associated : PixelAlphaRepresentation.Unassociated;
/// <summary>
/// Checks whether two <see cref="Color"/> structures are equal.
/// </summary>
@ -80,21 +117,36 @@ public readonly partial struct Color : IEquatable<Color>
{
// Avoid boxing in case we can convert to Vector4 safely and efficiently
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
{
return new Color(source.ToScaledVector4());
Vector4 vector = source.ToScaledVector4();
Vector4 unassociated = info.AlphaRepresentation == PixelAlphaRepresentation.Associated
? PixelOperations<TPixel>.Instance.ToUnassociatedScaledVector4(source)
: vector;
return new Color(vector, unassociated, info.AlphaRepresentation);
}
return new Color(source);
return new Color(source, info.AlphaRepresentation);
}
/// <summary>
/// Creates a <see cref="Color"/> from a generic scaled <see cref="Vector4"/>.
/// </summary>
/// <param name="source">The vector to load the pixel from.</param>
/// <param name="source">The unassociated vector to load the color from.</param>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color FromScaledVector(Vector4 source) => new(source, PixelAlphaRepresentation.Unassociated);
/// <summary>
/// Creates a <see cref="Color"/> from a generic scaled <see cref="Vector4"/> with the specified alpha representation.
/// </summary>
/// <param name="source">The vector to load the color from.</param>
/// <param name="alphaRepresentation">The alpha representation of <paramref name="source"/>.</param>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color FromScaledVector(Vector4 source) => new(source);
public static Color FromScaledVector(Vector4 source, PixelAlphaRepresentation alphaRepresentation) => new(source, alphaRepresentation);
/// <summary>
/// Bulk converts a span of generic scaled <see cref="Vector4"/> to a span of <see cref="Color"/>.
@ -103,11 +155,23 @@ public readonly partial struct Color : IEquatable<Color>
/// <param name="destination">The destination color span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromScaledVector(ReadOnlySpan<Vector4> source, Span<Color> destination)
=> FromScaledVector(source, destination, PixelAlphaRepresentation.Unassociated);
/// <summary>
/// Bulk converts a span of generic scaled <see cref="Vector4"/> values with the specified alpha representation
/// to a span of <see cref="Color"/> values.
/// </summary>
/// <param name="source">The source vector span.</param>
/// <param name="destination">The destination color span.</param>
/// <param name="alphaRepresentation">The alpha representation of the source vectors.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromScaledVector(ReadOnlySpan<Vector4> source, Span<Color> destination, PixelAlphaRepresentation alphaRepresentation)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
for (int i = 0; i < source.Length; i++)
{
destination[i] = FromScaledVector(source[i]);
destination[i] = FromScaledVector(source[i], alphaRepresentation);
}
}
@ -125,18 +189,23 @@ public readonly partial struct Color : IEquatable<Color>
// Avoid boxing in case we can convert to Vector4 safely and efficiently
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
{
bool isAssociated = info.AlphaRepresentation == PixelAlphaRepresentation.Associated;
for (int i = 0; i < source.Length; i++)
{
destination[i] = FromScaledVector(source[i].ToScaledVector4());
Vector4 vector = source[i].ToScaledVector4();
Vector4 unassociated = isAssociated ? PixelOperations<TPixel>.Instance.ToUnassociatedScaledVector4(source[i]) : vector;
destination[i] = new Color(vector, unassociated, info.AlphaRepresentation);
}
}
else
{
for (int i = 0; i < source.Length; i++)
{
destination[i] = new Color(source[i]);
destination[i] = new Color(source[i], info.AlphaRepresentation);
}
}
}
@ -276,13 +345,30 @@ public readonly partial struct Color : IEquatable<Color>
/// Alters the alpha channel of the color, returning a new instance.
/// </summary>
/// <param name="alpha">The new value of alpha [0..1].</param>
/// <returns>The color having it's alpha channel altered.</returns>
/// <returns>The color having its alpha channel altered.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color WithAlpha(float alpha)
{
Vector4 v = this.ToScaledVector4();
v.W = alpha;
return FromScaledVector(v);
float clampedAlpha = Numerics.Clamp(alpha, 0, 1);
if (this.isAssociated)
{
Vector4 unassociated = this.boxedHighPrecisionPixel is null ? this.unassociatedData : v;
if (this.boxedHighPrecisionPixel is not null)
{
Numerics.UnPremultiply(ref unassociated);
}
unassociated.W = clampedAlpha;
Vector4 associated = unassociated;
Numerics.Premultiply(ref associated);
return new Color(associated, unassociated, PixelAlphaRepresentation.Associated);
}
v.W = clampedAlpha;
return FromScaledVector(v, PixelAlphaRepresentation.Unassociated);
}
/// <summary>
@ -296,9 +382,7 @@ public readonly partial struct Color : IEquatable<Color>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ToHex(ColorHexFormat format = ColorHexFormat.Rgba)
{
Rgba32 rgba = (this.boxedHighPrecisionPixel is not null)
? this.boxedHighPrecisionPixel.ToRgba32()
: Rgba32.FromScaledVector4(this.data);
Rgba32 rgba = this.ToPixel<Rgba32>();
uint hexOrder = format switch
{
@ -327,17 +411,23 @@ public readonly partial struct Color : IEquatable<Color>
return pixel;
}
if (this.boxedHighPrecisionPixel is null)
Vector4 vector = this.boxedHighPrecisionPixel is null
? this.isAssociated ? this.unassociatedData : this.data
: this.boxedHighPrecisionPixel.ToScaledVector4();
if (this.isAssociated && this.boxedHighPrecisionPixel is not null)
{
return TPixel.FromScaledVector4(this.data);
Numerics.UnPremultiply(ref vector);
}
return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
// Destination pixel operations own association because RGB must use the alpha value representable by that destination format.
return PixelOperations<TPixel>.Instance.FromUnassociatedScaledVector4(vector);
}
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation,
/// preserving the <see cref="AlphaRepresentation"/>, with values scaled and clamped between
/// <value>0</value> and <value>1</value>.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector4"/>.</returns>
@ -377,10 +467,11 @@ public readonly partial struct Color : IEquatable<Color>
{
if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
{
return this.data == other.data;
return this.data == other.data && this.isAssociated == other.isAssociated;
}
return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
return this.isAssociated == other.isAssociated
&& this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
}
/// <inheritdoc />
@ -392,10 +483,10 @@ public readonly partial struct Color : IEquatable<Color>
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.GetHashCode();
return HashCode.Combine(this.data, this.isAssociated);
}
return this.boxedHighPrecisionPixel.GetHashCode();
return HashCode.Combine(this.boxedHighPrecisionPixel.ToScaledVector4(), this.isAssociated);
}
/// <summary>

32
src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs

@ -41,38 +41,58 @@ internal static partial class SimdUtils
{
DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!");
HwIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref destination);
HwIntrinsics.FloatToByteSaturateReduce(ref source, ref destination, byte.MaxValue);
if (source.Length > 0)
{
ConvertNormalizedFloatToByteRemainder(source, destination);
ConvertFloatToByteRemainder(source, destination, byte.MaxValue);
}
}
/// <summary>
/// Converts byte-magnitude floating-point values to bytes using saturating round-to-nearest with midpoint values away from zero.
/// </summary>
/// <param name="source">The source byte magnitudes.</param>
/// <param name="destination">The destination bytes.</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void FloatToByteSaturate(ReadOnlySpan<float> source, Span<byte> destination)
{
DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!");
HwIntrinsics.FloatToByteSaturateReduce(ref source, ref destination, 1F);
if (source.Length > 0)
{
ConvertFloatToByteRemainder(source, destination, 1F);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan<byte> source, Span<float> destination)
{
const float scale = 1F / byte.MaxValue;
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref float dBase = ref MemoryMarshal.GetReference(destination);
for (int i = 0; i < source.Length; i++)
{
Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) / 255f;
// Match the SIMD conversion so one span cannot contain different float representations of the same byte value.
Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) * scale;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan<float> source, Span<byte> destination)
private static void ConvertFloatToByteRemainder(ReadOnlySpan<float> source, Span<byte> destination, float scale)
{
ref float sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
for (int i = 0; i < source.Length; i++)
{
Unsafe.Add(ref dBase, (uint)i) = ConvertToByte(Unsafe.Add(ref sBase, (uint)i));
Unsafe.Add(ref dBase, (uint)i) = ConvertToByte(Unsafe.Add(ref sBase, (uint)i), scale);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255f) + 0.5f, 0, 255f);
private static byte ConvertToByte(float value, float scale) => (byte)Numerics.Clamp((value * scale) + .5F, 0, byte.MaxValue);
}

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

@ -834,6 +834,19 @@ internal static partial class SimdUtils
internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan<float> source,
ref Span<byte> destination)
=> FloatToByteSaturateReduce(ref source, ref destination, byte.MaxValue);
/// <summary>
/// Converts as many scaled floating-point values as possible to bytes and retains the unconverted remainder.
/// </summary>
/// <param name="source">The source buffer.</param>
/// <param name="destination">The destination buffer.</param>
/// <param name="scaleFactor">The factor applied before conversion.</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void FloatToByteSaturateReduce(
ref ReadOnlySpan<float> source,
ref Span<byte> destination,
float scaleFactor)
{
DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!");
@ -858,9 +871,10 @@ internal static partial class SimdUtils
if (adjustedCount > 0)
{
NormalizedFloatToByteSaturate(
FloatToByteSaturate(
source[..adjustedCount],
destination[..adjustedCount]);
destination[..adjustedCount],
scaleFactor);
source = source[adjustedCount..];
destination = destination[adjustedCount..];
@ -880,6 +894,18 @@ internal static partial class SimdUtils
internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> destination)
=> FloatToByteSaturate(source, destination, byte.MaxValue);
/// <summary>
/// Converts scaled floating-point values to bytes using saturating round-to-nearest with midpoint values away from zero.
/// </summary>
/// <param name="source">The source buffer.</param>
/// <param name="destination">The destination buffer.</param>
/// <param name="scaleFactor">The factor applied before conversion.</param>
internal static void FloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> destination,
float scaleFactor)
{
if (Vector512.IsHardwareAccelerated && Avx512BW.IsSupported)
{
@ -890,7 +916,7 @@ internal static partial class SimdUtils
ref Vector512<float> sourceBase = ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref Vector512<byte> destinationBase = ref Unsafe.As<byte, Vector512<byte>>(ref MemoryMarshal.GetReference(destination));
Vector512<float> scale = Vector512.Create((float)byte.MaxValue);
Vector512<float> scale = Vector512.Create(scaleFactor);
Vector512<int> mask = PermuteMaskDeinterleave16x32();
for (nuint i = 0; i < n; i++)
@ -902,10 +928,10 @@ internal static partial class SimdUtils
Vector512<float> f2 = scale * Unsafe.Add(ref s, 2);
Vector512<float> f3 = scale * Unsafe.Add(ref s, 3);
Vector512<int> w0 = Vector512_.ConvertToInt32RoundToEven(f0);
Vector512<int> w1 = Vector512_.ConvertToInt32RoundToEven(f1);
Vector512<int> w2 = Vector512_.ConvertToInt32RoundToEven(f2);
Vector512<int> w3 = Vector512_.ConvertToInt32RoundToEven(f3);
Vector512<int> w0 = Vector512_.ConvertToInt32RoundAwayFromZero(f0);
Vector512<int> w1 = Vector512_.ConvertToInt32RoundAwayFromZero(f1);
Vector512<int> w2 = Vector512_.ConvertToInt32RoundAwayFromZero(f2);
Vector512<int> w3 = Vector512_.ConvertToInt32RoundAwayFromZero(f3);
Vector512<short> u0 = Avx512BW.PackSignedSaturate(w0, w1);
Vector512<short> u1 = Avx512BW.PackSignedSaturate(w2, w3);
@ -924,7 +950,7 @@ internal static partial class SimdUtils
ref Vector256<float> sourceBase = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref Vector256<byte> destinationBase = ref Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(destination));
Vector256<float> scale = Vector256.Create((float)byte.MaxValue);
Vector256<float> scale = Vector256.Create(scaleFactor);
Vector256<int> mask = PermuteMaskDeinterleave8x32();
for (nuint i = 0; i < n; i++)
@ -936,10 +962,10 @@ internal static partial class SimdUtils
Vector256<float> f2 = scale * Unsafe.Add(ref s, 2);
Vector256<float> f3 = scale * Unsafe.Add(ref s, 3);
Vector256<int> w0 = Vector256_.ConvertToInt32RoundToEven(f0);
Vector256<int> w1 = Vector256_.ConvertToInt32RoundToEven(f1);
Vector256<int> w2 = Vector256_.ConvertToInt32RoundToEven(f2);
Vector256<int> w3 = Vector256_.ConvertToInt32RoundToEven(f3);
Vector256<int> w0 = Vector256_.ConvertToInt32RoundAwayFromZero(f0);
Vector256<int> w1 = Vector256_.ConvertToInt32RoundAwayFromZero(f1);
Vector256<int> w2 = Vector256_.ConvertToInt32RoundAwayFromZero(f2);
Vector256<int> w3 = Vector256_.ConvertToInt32RoundAwayFromZero(f3);
Vector256<short> u0 = Avx2.PackSignedSaturate(w0, w1);
Vector256<short> u1 = Avx2.PackSignedSaturate(w2, w3);
@ -959,7 +985,7 @@ internal static partial class SimdUtils
ref Vector128<float> sourceBase = ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(source));
ref Vector128<byte> destinationBase = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(destination));
Vector128<float> scale = Vector128.Create((float)byte.MaxValue);
Vector128<float> scale = Vector128.Create(scaleFactor);
Vector128<int> min = Vector128<int>.Zero;
Vector128<int> max = Vector128.Create((int)byte.MaxValue);
@ -972,10 +998,10 @@ internal static partial class SimdUtils
Vector128<float> f2 = scale * Unsafe.Add(ref s, 2);
Vector128<float> f3 = scale * Unsafe.Add(ref s, 3);
Vector128<int> w0 = Vector128_.ConvertToInt32RoundToEven(f0);
Vector128<int> w1 = Vector128_.ConvertToInt32RoundToEven(f1);
Vector128<int> w2 = Vector128_.ConvertToInt32RoundToEven(f2);
Vector128<int> w3 = Vector128_.ConvertToInt32RoundToEven(f3);
Vector128<int> w0 = Vector128_.ConvertToInt32RoundAwayFromZero(f0);
Vector128<int> w1 = Vector128_.ConvertToInt32RoundAwayFromZero(f1);
Vector128<int> w2 = Vector128_.ConvertToInt32RoundAwayFromZero(f2);
Vector128<int> w3 = Vector128_.ConvertToInt32RoundAwayFromZero(f3);
w0 = Vector128_.Clamp(w0, min, max);
w1 = Vector128_.Clamp(w1, min, max);

25
src/ImageSharp/Common/Helpers/Vector128Utilities.cs

@ -307,6 +307,31 @@ internal static class Vector128_
return Vector128.ConvertToInt32(val_2p23_f32 | sign);
}
/// <summary>
/// Converts all values in <paramref name="vector"/> to signed 32-bit integers, rounding midpoint values away from zero.
/// </summary>
/// <param name="vector">The values to convert.</param>
/// <returns>The converted integer values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<int> ConvertToInt32RoundAwayFromZero(Vector128<float> vector)
{
if (Sse2.IsSupported)
{
// The x86 conversion truncates, so adding one half with each lane's sign implements round-to-nearest with midpoint values away from zero.
Vector128<float> x86Adjustment = Vector128.Create(.5F) | (vector & Vector128.Create(-0F));
return Sse2.ConvertToVector128Int32WithTruncation(vector + x86Adjustment);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ConvertToInt32RoundAwayFromZero(vector);
}
Vector128<float> sign = vector & Vector128.Create(-0F);
Vector128<float> fallbackAdjustment = Vector128.Create(.5F) | sign;
return Vector128.ConvertToInt32(vector + fallbackAdjustment);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.

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

@ -73,6 +73,26 @@ internal static class Vector256_
return Vector256.ConvertToInt32(val_2p23_f32 | sign);
}
/// <summary>
/// Converts all values in <paramref name="vector"/> to signed 32-bit integers, rounding midpoint values away from zero.
/// </summary>
/// <param name="vector">The values to convert.</param>
/// <returns>The converted integer values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> ConvertToInt32RoundAwayFromZero(Vector256<float> vector)
{
if (Avx.IsSupported)
{
// The x86 conversion truncates, so adding one half with each lane's sign implements round-to-nearest with midpoint values away from zero.
Vector256<float> x86Adjustment = Vector256.Create(.5F) | (vector & Vector256.Create(-0F));
return Avx.ConvertToVector256Int32WithTruncation(vector + x86Adjustment);
}
Vector256<float> sign = vector & Vector256.Create(-0F);
Vector256<float> fallbackAdjustment = Vector256.Create(.5F) | sign;
return Vector256.ConvertToInt32(vector + fallbackAdjustment);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.

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

@ -59,6 +59,19 @@ internal static class Vector512_
public static Vector512<int> ConvertToInt32RoundToEven(Vector512<float> vector)
=> Avx512F.ConvertToVector512Int32(vector);
/// <summary>
/// Converts all values in <paramref name="vector"/> to signed 32-bit integers, rounding midpoint values away from zero.
/// </summary>
/// <param name="vector">The values to convert.</param>
/// <returns>The converted integer values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<int> ConvertToInt32RoundAwayFromZero(Vector512<float> vector)
{
// The x86 conversion truncates, so adding one half with each lane's sign implements round-to-nearest with midpoint values away from zero.
Vector512<float> half = Vector512.Create(.5F) | (vector & Vector512.Create(-0F));
return Avx512F.ConvertToVector512Int32WithTruncation(vector + half);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.

18
src/ImageSharp/ImageSharp.csproj

@ -141,6 +141,16 @@
<AutoGen>True</AutoGen>
<DependentUpon>DefaultPixelBlenders.Generated.tt</DependentUpon>
</Compile>
<Compile Update="PixelFormats\PixelBlenders\AssociatedAlphaPixelBlenders.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>AssociatedAlphaPixelBlenders.Generated.tt</DependentUpon>
</Compile>
<Compile Update="PixelFormats\PixelBlenders\AssociatedAlphaPorterDuffFunctions.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>AssociatedAlphaPorterDuffFunctions.Generated.tt</DependentUpon>
</Compile>
<Compile Update="PixelFormats\PixelBlenders\PorterDuffFunctions.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@ -221,6 +231,14 @@
<LastGenOutput>DefaultPixelBlenders.Generated.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="PixelFormats\PixelBlenders\AssociatedAlphaPixelBlenders.Generated.tt">
<LastGenOutput>AssociatedAlphaPixelBlenders.Generated.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="PixelFormats\PixelBlenders\AssociatedAlphaPorterDuffFunctions.Generated.tt">
<LastGenOutput>AssociatedAlphaPorterDuffFunctions.Generated.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="Formats\_Generated\ImageExtensions.Save.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ImageExtensions.Save.cs</LastGenOutput>

258
src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs

@ -0,0 +1,258 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Provides bulk operations for pixel formats that store associated alpha.
/// </summary>
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
internal class AssociatedAlphaPixelOperations<TPixel> : PixelOperations<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc />
public override PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode)
=> AssociatedAlphaPixelBlenders<TPixel>.GetPixelBlender(colorMode, alphaMode);
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(TPixel source)
{
Vector4 vector = source.ToScaledVector4();
Numerics.UnPremultiply(ref vector);
return vector;
}
/// <inheritdoc />
internal override TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Associate(source));
/// <summary>
/// Converts an associated scaled vector to a destination pixel after associating RGB with the alpha value the destination stores.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The destination pixel.</returns>
public virtual TPixel FromAssociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Reassociate(source));
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(
Configuration configuration,
ReadOnlySpan<TPixel> source,
Span<Vector4> destination)
{
this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale);
Numerics.UnPremultiply(destination[..source.Length]);
}
/// <inheritdoc />
internal override void ToAssociatedScaledVector4(
Configuration configuration,
ReadOnlySpan<TPixel> source,
Span<Vector4> destination)
=> this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale);
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(
Configuration configuration,
Span<Vector4> source,
Span<TPixel> destination)
{
source = source[..destination.Length];
for (int i = 0; i < source.Length; i++)
{
source[i] = Associate(source[i]);
}
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Converts associated scaled vectors to destination pixels after associating RGB with the alpha values the destination stores.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The associated scaled vectors.</param>
/// <param name="destination">The destination pixels.</param>
public virtual void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<TPixel> destination)
{
source = source[..destination.Length];
for (int i = 0; i < source.Length; i++)
{
source[i] = Reassociate(source[i]);
}
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> source,
Span<TPixel> destination)
{
const int sliceLength = 1024;
int numberOfSlices = source.Length / sliceLength;
using IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(sliceLength);
Span<Vector4> vectorSpan = tempVectors.GetSpan();
// Convert through unassociated vectors so the destination operation can quantize alpha to its own storage before associating RGB.
for (int i = 0; i < numberOfSlices; i++)
{
int start = i * sliceLength;
ReadOnlySpan<TSourcePixel> sourceSlice = source.Slice(start, sliceLength);
Span<TPixel> destinationSlice = destination.Slice(start, sliceLength);
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan);
this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice);
}
int endOfCompleteSlices = numberOfSlices * sliceLength;
int remainder = source.Length - endOfCompleteSlices;
if (remainder > 0)
{
ReadOnlySpan<TSourcePixel> sourceSlice = source[endOfCompleteSlices..];
Span<TPixel> destinationSlice = destination[endOfCompleteSlices..];
vectorSpan = vectorSpan[..remainder];
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan);
this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice);
}
}
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
Span<Vector4> sourceVectors,
Span<TPixel> destination,
PixelConversionModifiers modifiers)
=> base.FromVector4Destructive(configuration, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Premultiply));
/// <inheritdoc />
public override void ToVector4(
Configuration configuration,
ReadOnlySpan<TPixel> source,
Span<Vector4> destinationVectors,
PixelConversionModifiers modifiers)
=> base.ToVector4(configuration, source, destinationVectors, modifiers.Remove(PixelConversionModifiers.Premultiply));
/// <inheritdoc />
public override void ToArgb32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Argb32> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToAbgr32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Abgr32> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToBgr24(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Bgr24> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToBgra32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Bgra32> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToL8(Configuration configuration, ReadOnlySpan<TPixel> source, Span<L8> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToL16(Configuration configuration, ReadOnlySpan<TPixel> source, Span<L16> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToLa16(Configuration configuration, ReadOnlySpan<TPixel> source, Span<La16> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToLa32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<La32> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToRgb24(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgb24> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToRgba32(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgba32> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToRgb48(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgb48> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToRgba64(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Rgba64> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <inheritdoc />
public override void ToBgra5551(Configuration configuration, ReadOnlySpan<TPixel> source, Span<Bgra5551> destination)
=> this.ConvertToUnassociated(configuration, source, destination);
/// <summary>
/// Converts associated source pixels to an unassociated destination format.
/// </summary>
/// <typeparam name="TDestinationPixel">The destination pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination pixels.</param>
private void ConvertToUnassociated<TDestinationPixel>(
Configuration configuration,
ReadOnlySpan<TPixel> source,
Span<TDestinationPixel> destination)
where TDestinationPixel : unmanaged, IPixel<TDestinationPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
ref TPixel sourceBase = ref MemoryMarshal.GetReference(source);
ref TDestinationPixel destinationBase = ref MemoryMarshal.GetReference(destination);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Vector4 vector = this.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i));
Unsafe.Add(ref destinationBase, i) = TDestinationPixel.FromScaledVector4(vector);
}
}
/// <summary>
/// Converts an unassociated scaled vector to the associated representation of the destination pixel type.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Associate(Vector4 source)
{
// Round-trip alpha through TPixel so the generic fallback associates RGB with the value the destination actually stores.
source.W = TPixel.FromScaledVector4(new Vector4(0, 0, 0, source.W)).ToScaledVector4().W;
Numerics.Premultiply(ref source);
return source;
}
/// <summary>
/// Reassociates a scaled vector with the alpha value the destination pixel can store.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The reassociated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Reassociate(Vector4 source)
{
float alpha = source.W;
if (alpha == 0)
{
return Vector4.Zero;
}
float storedAlpha = TPixel.FromScaledVector4(new Vector4(0, 0, 0, alpha)).ToScaledVector4().W;
// Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization.
source *= storedAlpha / alpha;
source.W = storedAlpha;
return source;
}
}

84152
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs

File diff suppressed because it is too large

847
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt

@ -0,0 +1,847 @@
<#
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// <auto-generated />
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// <summary>
/// Provides generated Porter-Duff blenders for associated-alpha pixel formats.
/// </summary>
internal static partial class AssociatedAlphaPixelBlenders<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
<#
var composers = new []{
"Src",
"SrcAtop",
"SrcOver",
"SrcIn",
"SrcOut",
"Dest",
"DestAtop",
"DestOver",
"DestIn",
"DestOut",
"Clear",
"Xor",
};
var blenders = new []{
"Normal",
"Multiply",
"Add",
"Subtract",
"Screen",
"Darken",
"Lighten",
"Overlay",
"HardLight"
};
foreach(var composer in composers) {
foreach(var blender in blenders) {
var blender_composer= $"{blender}{composer}";
#>
/// <summary>
/// A pixel blender that implements the "<#= blender_composer#>" composition equation.
/// </summary>
public sealed class <#= blender_composer#> : AssociatedAlphaPixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>();
/// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
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));
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=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] = AssociatedAlphaPorterDuffFunctions.<#=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>
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));
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=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);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount)
{
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));
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 = AssociatedAlphaPorterDuffFunctions.<#=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] = AssociatedAlphaPorterDuffFunctions.<#=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>
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));
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);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
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 = AssociatedAlphaPorterDuffFunctions.<#=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] = AssociatedAlphaPorterDuffFunctions.<#=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>
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);
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);
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=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, 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;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
{
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 = AssociatedAlphaPorterDuffFunctions.<#=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] = AssociatedAlphaPorterDuffFunctions.<#=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>
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);
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);
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=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, 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;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
/// <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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.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 = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
}
<#
}
}
#>
}

168
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs

@ -0,0 +1,168 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// <summary>
/// Provides pixel blenders for formats that store associated alpha.
/// </summary>
internal static partial class AssociatedAlphaPixelBlenders<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Gets the blender for the requested color blending and alpha composition modes.
/// </summary>
/// <param name="colorMode">The color blending mode.</param>
/// <param name="alphaMode">The alpha composition mode.</param>
/// <returns>The pixel blender.</returns>
public static PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode)
{
return alphaMode switch
{
PixelAlphaCompositionMode.Src => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplySrc.Instance,
PixelColorBlendingMode.Add => AddSrc.Instance,
PixelColorBlendingMode.Subtract => SubtractSrc.Instance,
PixelColorBlendingMode.Screen => ScreenSrc.Instance,
PixelColorBlendingMode.Darken => DarkenSrc.Instance,
PixelColorBlendingMode.Lighten => LightenSrc.Instance,
PixelColorBlendingMode.Overlay => OverlaySrc.Instance,
PixelColorBlendingMode.HardLight => HardLightSrc.Instance,
_ => NormalSrc.Instance,
},
PixelAlphaCompositionMode.SrcAtop => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplySrcAtop.Instance,
PixelColorBlendingMode.Add => AddSrcAtop.Instance,
PixelColorBlendingMode.Subtract => SubtractSrcAtop.Instance,
PixelColorBlendingMode.Screen => ScreenSrcAtop.Instance,
PixelColorBlendingMode.Darken => DarkenSrcAtop.Instance,
PixelColorBlendingMode.Lighten => LightenSrcAtop.Instance,
PixelColorBlendingMode.Overlay => OverlaySrcAtop.Instance,
PixelColorBlendingMode.HardLight => HardLightSrcAtop.Instance,
_ => NormalSrcAtop.Instance,
},
PixelAlphaCompositionMode.SrcIn => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplySrcIn.Instance,
PixelColorBlendingMode.Add => AddSrcIn.Instance,
PixelColorBlendingMode.Subtract => SubtractSrcIn.Instance,
PixelColorBlendingMode.Screen => ScreenSrcIn.Instance,
PixelColorBlendingMode.Darken => DarkenSrcIn.Instance,
PixelColorBlendingMode.Lighten => LightenSrcIn.Instance,
PixelColorBlendingMode.Overlay => OverlaySrcIn.Instance,
PixelColorBlendingMode.HardLight => HardLightSrcIn.Instance,
_ => NormalSrcIn.Instance,
},
PixelAlphaCompositionMode.SrcOut => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplySrcOut.Instance,
PixelColorBlendingMode.Add => AddSrcOut.Instance,
PixelColorBlendingMode.Subtract => SubtractSrcOut.Instance,
PixelColorBlendingMode.Screen => ScreenSrcOut.Instance,
PixelColorBlendingMode.Darken => DarkenSrcOut.Instance,
PixelColorBlendingMode.Lighten => LightenSrcOut.Instance,
PixelColorBlendingMode.Overlay => OverlaySrcOut.Instance,
PixelColorBlendingMode.HardLight => HardLightSrcOut.Instance,
_ => NormalSrcOut.Instance,
},
PixelAlphaCompositionMode.Dest => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyDest.Instance,
PixelColorBlendingMode.Add => AddDest.Instance,
PixelColorBlendingMode.Subtract => SubtractDest.Instance,
PixelColorBlendingMode.Screen => ScreenDest.Instance,
PixelColorBlendingMode.Darken => DarkenDest.Instance,
PixelColorBlendingMode.Lighten => LightenDest.Instance,
PixelColorBlendingMode.Overlay => OverlayDest.Instance,
PixelColorBlendingMode.HardLight => HardLightDest.Instance,
_ => NormalDest.Instance,
},
PixelAlphaCompositionMode.DestAtop => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyDestAtop.Instance,
PixelColorBlendingMode.Add => AddDestAtop.Instance,
PixelColorBlendingMode.Subtract => SubtractDestAtop.Instance,
PixelColorBlendingMode.Screen => ScreenDestAtop.Instance,
PixelColorBlendingMode.Darken => DarkenDestAtop.Instance,
PixelColorBlendingMode.Lighten => LightenDestAtop.Instance,
PixelColorBlendingMode.Overlay => OverlayDestAtop.Instance,
PixelColorBlendingMode.HardLight => HardLightDestAtop.Instance,
_ => NormalDestAtop.Instance,
},
PixelAlphaCompositionMode.DestOver => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyDestOver.Instance,
PixelColorBlendingMode.Add => AddDestOver.Instance,
PixelColorBlendingMode.Subtract => SubtractDestOver.Instance,
PixelColorBlendingMode.Screen => ScreenDestOver.Instance,
PixelColorBlendingMode.Darken => DarkenDestOver.Instance,
PixelColorBlendingMode.Lighten => LightenDestOver.Instance,
PixelColorBlendingMode.Overlay => OverlayDestOver.Instance,
PixelColorBlendingMode.HardLight => HardLightDestOver.Instance,
_ => NormalDestOver.Instance,
},
PixelAlphaCompositionMode.DestIn => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyDestIn.Instance,
PixelColorBlendingMode.Add => AddDestIn.Instance,
PixelColorBlendingMode.Subtract => SubtractDestIn.Instance,
PixelColorBlendingMode.Screen => ScreenDestIn.Instance,
PixelColorBlendingMode.Darken => DarkenDestIn.Instance,
PixelColorBlendingMode.Lighten => LightenDestIn.Instance,
PixelColorBlendingMode.Overlay => OverlayDestIn.Instance,
PixelColorBlendingMode.HardLight => HardLightDestIn.Instance,
_ => NormalDestIn.Instance,
},
PixelAlphaCompositionMode.DestOut => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyDestOut.Instance,
PixelColorBlendingMode.Add => AddDestOut.Instance,
PixelColorBlendingMode.Subtract => SubtractDestOut.Instance,
PixelColorBlendingMode.Screen => ScreenDestOut.Instance,
PixelColorBlendingMode.Darken => DarkenDestOut.Instance,
PixelColorBlendingMode.Lighten => LightenDestOut.Instance,
PixelColorBlendingMode.Overlay => OverlayDestOut.Instance,
PixelColorBlendingMode.HardLight => HardLightDestOut.Instance,
_ => NormalDestOut.Instance,
},
PixelAlphaCompositionMode.Clear => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyClear.Instance,
PixelColorBlendingMode.Add => AddClear.Instance,
PixelColorBlendingMode.Subtract => SubtractClear.Instance,
PixelColorBlendingMode.Screen => ScreenClear.Instance,
PixelColorBlendingMode.Darken => DarkenClear.Instance,
PixelColorBlendingMode.Lighten => LightenClear.Instance,
PixelColorBlendingMode.Overlay => OverlayClear.Instance,
PixelColorBlendingMode.HardLight => HardLightClear.Instance,
_ => NormalClear.Instance,
},
PixelAlphaCompositionMode.Xor => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplyXor.Instance,
PixelColorBlendingMode.Add => AddXor.Instance,
PixelColorBlendingMode.Subtract => SubtractXor.Instance,
PixelColorBlendingMode.Screen => ScreenXor.Instance,
PixelColorBlendingMode.Darken => DarkenXor.Instance,
PixelColorBlendingMode.Lighten => LightenXor.Instance,
PixelColorBlendingMode.Overlay => OverlayXor.Instance,
PixelColorBlendingMode.HardLight => HardLightXor.Instance,
_ => NormalXor.Instance,
},
_ => colorMode switch
{
PixelColorBlendingMode.Multiply => MultiplySrcOver.Instance,
PixelColorBlendingMode.Add => AddSrcOver.Instance,
PixelColorBlendingMode.Subtract => SubtractSrcOver.Instance,
PixelColorBlendingMode.Screen => ScreenSrcOver.Instance,
PixelColorBlendingMode.Darken => DarkenSrcOver.Instance,
PixelColorBlendingMode.Lighten => LightenSrcOver.Instance,
PixelColorBlendingMode.Overlay => OverlaySrcOver.Instance,
PixelColorBlendingMode.HardLight => HardLightSrcOver.Instance,
_ => NormalSrcOver.Instance,
},
};
}
}

46
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// <summary>
/// Provides the vector representation used to blend pixels that store associated alpha.
/// </summary>
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
internal abstract class AssociatedAlphaPixelBlender<TPixel> : PixelBlender<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// Associated blenders are created only by AssociatedAlphaPixelOperations, so the cached cast establishes the format-specific output boundary once per closed pixel type.
private static readonly AssociatedAlphaPixelOperations<TPixel> Operations = (AssociatedAlphaPixelOperations<TPixel>)PixelOperations<TPixel>.Instance;
/// <inheritdoc />
protected override void ToBlendVector4<TPixelSource>(
Configuration configuration,
ReadOnlySpan<TPixelSource> source,
Span<Vector4> destination)
{
// Selecting the source representation once per row avoids a format check for every blended pixel.
PixelOperations<TPixelSource>.Instance.ToAssociatedScaledVector4(configuration, source, destination);
}
/// <inheritdoc />
protected override Vector4 ToBlendVector4(TPixel source) => source.ToScaledVector4();
/// <summary>
/// Converts an associated blend result to the destination pixel representation.
/// </summary>
/// <param name="source">The associated blend result.</param>
/// <returns>The destination pixel.</returns>
public static TPixel FromBlendVector4(Vector4 source) => Operations.FromAssociatedScaledVector4(source);
/// <inheritdoc />
protected override void FromBlendVector4(
Configuration configuration,
Span<Vector4> source,
Span<TPixel> destination)
{
Operations.FromAssociatedScaledVector4(configuration, source, destination);
}
}

5035
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs

File diff suppressed because it is too large

138
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt

@ -0,0 +1,138 @@
<#
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ output extension=".cs" #>
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// <auto-generated />
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
internal static partial class AssociatedAlphaPorterDuffFunctions
{
<#
foreach (string composer in Composers)
{
foreach (string blender in Blenders)
{
string function = blender + composer;
foreach (string vectorType in VectorTypes)
{
string opacityType = vectorType == "Vector4" ? "float" : vectorType;
string zero = vectorType == "Vector4" ? "Vector4.Zero" : vectorType.Replace("<float>", "<float>.Zero");
#>
/// <summary>
/// Returns the associated-alpha result of the "<#= function #>" compositing equation.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <param name="opacity">The source opacity in the range 0 through 1.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= vectorType #> <#= function #>(<#= vectorType #> backdrop, <#= vectorType #> source, <#= opacityType #> opacity)
{
<#
if (composer != "Dest" && composer != "Clear")
{
#>
// Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour.
source *= opacity;
<#
}
#>
return <#= GetComposition(blender, composer, zero) #>;
}
<#
}
}
}
#>
}
<#+
private static readonly string[] Composers =
{
"Src",
"SrcAtop",
"SrcOver",
"SrcIn",
"SrcOut",
"Dest",
"DestAtop",
"DestOver",
"DestIn",
"DestOut",
"Clear",
"Xor",
};
private static readonly string[] Blenders =
{
"Normal",
"Multiply",
"Add",
"Subtract",
"Screen",
"Darken",
"Lighten",
"Overlay",
"HardLight",
};
private static readonly string[] VectorTypes =
{
"Vector4",
"Vector256<float>",
"Vector512<float>",
};
private static string GetComposition(string blender, string composer, string zero)
{
bool normal = blender == "Normal";
switch (composer)
{
case "Src":
return "source";
case "SrcAtop":
return normal
? "AtopNormal(backdrop, source)"
: $"Atop(backdrop, source, {blender}(backdrop, source))";
case "SrcOver":
return normal
? "OverNormal(backdrop, source)"
: $"Over(backdrop, source, {blender}(backdrop, source))";
case "SrcIn":
return "In(backdrop, source)";
case "SrcOut":
return "Out(backdrop, source)";
case "Dest":
return "backdrop";
case "DestAtop":
return normal
? "AtopNormal(source, backdrop)"
: $"Atop(source, backdrop, {blender}(source, backdrop))";
case "DestOver":
return normal
? "OverNormal(source, backdrop)"
: $"Over(source, backdrop, {blender}(source, backdrop))";
case "DestIn":
return "In(source, backdrop)";
case "DestOut":
return "Out(source, backdrop)";
case "Clear":
return zero;
default:
return "Xor(backdrop, source)";
}
}
#>

546
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs

@ -0,0 +1,546 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// <summary>
/// Provides Porter-Duff composition functions for associated-alpha vectors.
/// </summary>
internal static partial class AssociatedAlphaPorterDuffFunctions
{
private const int BlendAlphaControl = 0b_10_00_10_00;
private const int ShuffleAlphaControl = 0b_11_11_11_11;
/// <summary>
/// Calculates the associated overlap term for Multiply blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Multiply(Vector4 backdrop, Vector4 source) => backdrop * source;
/// <inheritdoc cref="Multiply(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Multiply(Vector256<float> backdrop, Vector256<float> source) => backdrop * source;
/// <inheritdoc cref="Multiply(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Multiply(Vector512<float> backdrop, Vector512<float> source) => backdrop * source;
/// <summary>
/// Calculates the associated overlap term for Add blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Add(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return Vector4.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha));
}
/// <inheritdoc cref="Add(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Add(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
return Vector256.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha));
}
/// <inheritdoc cref="Add(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Add(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
return Vector512.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha));
}
/// <summary>
/// Calculates the associated overlap term for Subtract blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Subtract(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return Vector4.Max(Vector4.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha));
}
/// <inheritdoc cref="Subtract(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Subtract(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
return Vector256.Max(Vector256<float>.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha));
}
/// <inheritdoc cref="Subtract(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Subtract(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
return Vector512.Max(Vector512<float>.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha));
}
/// <summary>
/// Calculates the associated overlap term for Screen blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Screen(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source);
}
/// <inheritdoc cref="Screen(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source);
}
/// <inheritdoc cref="Screen(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Screen(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source);
}
/// <summary>
/// Calculates the associated overlap term for Darken blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Darken(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return Vector4.Min(backdrop * sourceAlpha, source * backdropAlpha);
}
/// <inheritdoc cref="Darken(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Darken(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
return Vector256.Min(backdrop * sourceAlpha, source * backdropAlpha);
}
/// <inheritdoc cref="Darken(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Darken(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
return Vector512.Min(backdrop * sourceAlpha, source * backdropAlpha);
}
/// <summary>
/// Calculates the associated overlap term for Lighten blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Lighten(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return Vector4.Max(backdrop * sourceAlpha, source * backdropAlpha);
}
/// <inheritdoc cref="Lighten(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lighten(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
return Vector256.Max(backdrop * sourceAlpha, source * backdropAlpha);
}
/// <inheritdoc cref="Lighten(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Lighten(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
return Vector512.Max(backdrop * sourceAlpha, source * backdropAlpha);
}
/// <summary>
/// Calculates the associated overlap term for Overlay blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Overlay(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return new Vector4(
OverlayValue(backdrop.X, backdropAlpha.X, source.X, sourceAlpha.X),
OverlayValue(backdrop.Y, backdropAlpha.Y, source.Y, sourceAlpha.Y),
OverlayValue(backdrop.Z, backdropAlpha.Z, source.Z, sourceAlpha.Z),
0F);
}
/// <inheritdoc cref="Overlay(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Overlay(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> left = (backdrop + backdrop) * source;
Vector256<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F));
Vector256<float> useRight = Avx.CompareGreaterThan(backdrop + backdrop, backdropAlpha);
return Avx.BlendVariable(left, right, useRight);
}
/// <inheritdoc cref="Overlay(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Overlay(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> left = (backdrop + backdrop) * source;
Vector512<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F));
Vector512<float> useRight = Avx512F.CompareGreaterThan(backdrop + backdrop, backdropAlpha);
return Vector512.ConditionalSelect(useRight, right, left);
}
/// <summary>
/// Calculates the associated overlap term for HardLight blending.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated overlap term.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 HardLight(Vector4 backdrop, Vector4 source)
{
Vector4 backdropAlpha = Numerics.PermuteW(backdrop);
Vector4 sourceAlpha = Numerics.PermuteW(source);
return new Vector4(
OverlayValue(source.X, sourceAlpha.X, backdrop.X, backdropAlpha.X),
OverlayValue(source.Y, sourceAlpha.Y, backdrop.Y, backdropAlpha.Y),
OverlayValue(source.Z, sourceAlpha.Z, backdrop.Z, backdropAlpha.Z),
0F);
}
/// <inheritdoc cref="HardLight(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> HardLight(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> left = (backdrop + backdrop) * source;
Vector256<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F));
Vector256<float> useRight = Avx.CompareGreaterThan(source + source, sourceAlpha);
return Avx.BlendVariable(left, right, useRight);
}
/// <inheritdoc cref="HardLight(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> HardLight(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> left = (backdrop + backdrop) * source;
Vector512<float> right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F));
Vector512<float> useRight = Avx512F.CompareGreaterThan(source + source, sourceAlpha);
return Vector512.ConditionalSelect(useRight, right, left);
}
/// <summary>
/// Composites an associated source over an associated destination without a color-blending function.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 OverNormal(Vector4 destination, Vector4 source)
{
// Associated source-over is Ps + Pb(1 - As); both color and alpha therefore use the same coefficient.
Vector4 sourceAlpha = Numerics.PermuteW(source);
return source + (destination * (Vector4.One - sourceAlpha));
}
/// <inheritdoc cref="OverNormal(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> OverNormal(Vector256<float> destination, Vector256<float> source)
{
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
return source + (destination * (Vector256.Create(1F) - sourceAlpha));
}
/// <inheritdoc cref="OverNormal(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> OverNormal(Vector512<float> destination, Vector512<float> source)
{
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
return source + (destination * (Vector512.Create(1F) - sourceAlpha));
}
/// <summary>
/// Composites an associated source atop an associated destination without a color-blending function.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 AtopNormal(Vector4 destination, Vector4 source)
{
// Source-atop retains the destination alpha while replacing its covered contribution with the source.
Vector4 sourceAlpha = Numerics.PermuteW(source);
Vector4 destinationAlpha = Numerics.PermuteW(destination);
return (source * destinationAlpha) + (destination * (Vector4.One - sourceAlpha));
}
/// <inheritdoc cref="AtopNormal(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> AtopNormal(Vector256<float> destination, Vector256<float> source)
{
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl);
return (source * destinationAlpha) + (destination * (Vector256.Create(1F) - sourceAlpha));
}
/// <inheritdoc cref="AtopNormal(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> AtopNormal(Vector512<float> destination, Vector512<float> source)
{
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
return (source * destinationAlpha) + (destination * (Vector512.Create(1F) - sourceAlpha));
}
/// <summary>
/// Composites an associated source over an associated destination using an unassociated blended color.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <param name="overlap">The associated overlap term produced by the color-blending function.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 overlap)
{
// The three terms cover destination-only, source-only, and overlapping color respectively.
Vector4 sourceAlpha = Numerics.PermuteW(source);
Vector4 destinationAlpha = Numerics.PermuteW(destination);
Vector4 result = (destination * (Vector4.One - sourceAlpha)) + (source * (Vector4.One - destinationAlpha)) + overlap;
Vector4 alpha = source + (destination * (Vector4.One - sourceAlpha));
return Numerics.WithW(result, alpha);
}
/// <inheritdoc cref="Over(Vector4, Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Over(Vector256<float> destination, Vector256<float> source, Vector256<float> overlap)
{
Vector256<float> one = Vector256.Create(1F);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl);
Vector256<float> result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap;
Vector256<float> alpha = source + (destination * (one - sourceAlpha));
return Avx.Blend(result, alpha, BlendAlphaControl);
}
/// <inheritdoc cref="Over(Vector4, Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Over(Vector512<float> destination, Vector512<float> source, Vector512<float> overlap)
{
Vector512<float> one = Vector512.Create(1F);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap;
Vector512<float> alpha = source + (destination * (one - sourceAlpha));
return Vector512.ConditionalSelect(AlphaMask512(), alpha, result);
}
/// <summary>
/// Composites an associated source atop an associated destination using an unassociated blended color.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <param name="overlap">The associated overlap term produced by the color-blending function.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 overlap)
{
// Atop discards source-only color and retains the destination alpha unchanged.
Vector4 sourceAlpha = Numerics.PermuteW(source);
Vector4 destinationAlpha = Numerics.PermuteW(destination);
Vector4 result = (destination * (Vector4.One - sourceAlpha)) + overlap;
return Numerics.WithW(result, destinationAlpha);
}
/// <inheritdoc cref="Atop(Vector4, Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Atop(Vector256<float> destination, Vector256<float> source, Vector256<float> overlap)
{
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl);
Vector256<float> result = (destination * (Vector256.Create(1F) - sourceAlpha)) + overlap;
return Avx.Blend(result, destinationAlpha, BlendAlphaControl);
}
/// <inheritdoc cref="Atop(Vector4, Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Atop(Vector512<float> destination, Vector512<float> source, Vector512<float> overlap)
{
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> result = (destination * (Vector512.Create(1F) - sourceAlpha)) + overlap;
return Vector512.ConditionalSelect(AlphaMask512(), destinationAlpha, result);
}
/// <summary>
/// Retains the associated source within the destination coverage.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 destination, Vector4 source) => source * Numerics.PermuteW(destination);
/// <inheritdoc cref="In(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> In(Vector256<float> destination, Vector256<float> source)
=> source * Avx.Permute(destination, ShuffleAlphaControl);
/// <inheritdoc cref="In(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> In(Vector512<float> destination, Vector512<float> source)
=> source * Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
/// <summary>
/// Retains the associated source outside the destination coverage.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 destination, Vector4 source)
=> source * (Vector4.One - Numerics.PermuteW(destination));
/// <inheritdoc cref="Out(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Out(Vector256<float> destination, Vector256<float> source)
=> source * (Vector256.Create(1F) - Avx.Permute(destination, ShuffleAlphaControl));
/// <inheritdoc cref="Out(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Out(Vector512<float> destination, Vector512<float> source)
=> source * (Vector512.Create(1F) - Vector512_.ShuffleNative(destination, ShuffleAlphaControl));
/// <summary>
/// Retains only the non-overlapping parts of two associated vectors.
/// </summary>
/// <param name="destination">The associated destination vector.</param>
/// <param name="source">The associated source vector.</param>
/// <returns>The associated composition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 destination, Vector4 source)
{
Vector4 sourceAlpha = Numerics.PermuteW(source);
Vector4 destinationAlpha = Numerics.PermuteW(destination);
return (source * (Vector4.One - destinationAlpha)) + (destination * (Vector4.One - sourceAlpha));
}
/// <inheritdoc cref="Xor(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Xor(Vector256<float> destination, Vector256<float> source)
{
Vector256<float> one = Vector256.Create(1F);
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl);
return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha));
}
/// <inheritdoc cref="Xor(Vector4, Vector4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Xor(Vector512<float> destination, Vector512<float> source)
{
Vector512<float> one = Vector512.Create(1F);
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha));
}
/// <summary>
/// Applies raster coverage to an associated composition result.
/// </summary>
/// <param name="backdrop">The associated backdrop vector.</param>
/// <param name="source">The associated composition result.</param>
/// <param name="coverage">The raster coverage in the range 0 through 1.</param>
/// <returns>The covered associated result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage)
{
// Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths.
return Vector128_.MultiplyAdd(backdrop.AsVector128(), (source - backdrop).AsVector128(), Vector128.Create(coverage)).AsVector4();
}
/// <inheritdoc cref="BlendWithCoverage(Vector4, Vector4, float)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> BlendWithCoverage(Vector256<float> backdrop, Vector256<float> source, Vector256<float> coverage)
=> Vector256_.MultiplyAdd(backdrop, source - backdrop, coverage);
/// <inheritdoc cref="BlendWithCoverage(Vector4, Vector4, float)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> BlendWithCoverage(Vector512<float> backdrop, Vector512<float> source, Vector512<float> coverage)
=> Vector512_.MultiplyAdd(backdrop, source - backdrop, coverage);
/// <summary>
/// Calculates one associated Overlay overlap component without recovering either straight component.
/// </summary>
/// <param name="backdrop">The associated backdrop component.</param>
/// <param name="backdropAlpha">The backdrop alpha.</param>
/// <param name="source">The associated source component.</param>
/// <param name="sourceAlpha">The source alpha.</param>
/// <returns>The associated overlap component.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float OverlayValue(float backdrop, float backdropAlpha, float source, float sourceAlpha)
{
// Comparing 2Pb with Ab is equivalent to comparing the straight backdrop component with one half.
return (backdrop + backdrop) <= backdropAlpha
? (backdrop + backdrop) * source
: (backdropAlpha * sourceAlpha) - (2F * (backdropAlpha - backdrop) * (sourceAlpha - source));
}
/// <summary>
/// Creates a SIMD lane mask selecting the alpha component of each packed vector.
/// </summary>
/// <returns>The alpha-component mask.</returns>
[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();
}

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

@ -338,7 +338,9 @@ internal static partial class PorterDuffFunctions
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);
// Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths.
Vector4 result = Vector128_.MultiplyAdd(backdropPremultiplied.AsVector128(), (sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage)).AsVector4();
Numerics.UnPremultiply(ref result);
return result;

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

@ -93,12 +93,12 @@ public abstract class PixelBlender<TPixel>
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.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.ToBlendVector4(configuration, source[..maxLength], sourceVectors);
this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -161,11 +161,11 @@ public abstract class PixelBlender<TPixel>
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
this.BlendFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -242,12 +242,12 @@ public abstract class PixelBlender<TPixel>
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.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.ToBlendVector4(configuration, source[..maxLength], sourceVectors);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -317,11 +317,11 @@ public abstract class PixelBlender<TPixel>
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount, coverage);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -463,12 +463,12 @@ public abstract class PixelBlender<TPixel>
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.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.ToBlendVector4(configuration, source[..maxLength], sourceVectors);
this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -499,11 +499,11 @@ public abstract class PixelBlender<TPixel>
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
this.BlendFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -660,12 +660,12 @@ public abstract class PixelBlender<TPixel>
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.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.ToBlendVector4(configuration, source[..maxLength], sourceVectors);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
@ -699,13 +699,47 @@ public abstract class PixelBlender<TPixel>
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount, coverage);
this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount, coverage);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
this.FromBlendVector4(configuration, destinationVectors, destination);
}
/// <summary>
/// Converts source pixels to the scaled-vector representation consumed by this blender.
/// </summary>
/// <typeparam name="TPixelSource">The source pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
protected virtual void ToBlendVector4<TPixelSource>(
Configuration configuration,
ReadOnlySpan<TPixelSource> source,
Span<Vector4> destination)
where TPixelSource : unmanaged, IPixel<TPixelSource>
=> PixelOperations<TPixelSource>.Instance.ToUnassociatedScaledVector4(configuration, source, destination);
/// <summary>
/// Converts a source pixel to the vector representation consumed by the blend functions.
/// </summary>
/// <param name="source">The source pixel.</param>
/// <returns>The source vector.</returns>
protected virtual Vector4 ToBlendVector4(TPixel source)
=> PixelOperations<TPixel>.Instance.ToUnassociatedScaledVector4(source);
/// <summary>
/// Converts blend results from this blender's scaled-vector representation to destination pixels.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
protected virtual void FromBlendVector4(
Configuration configuration,
Span<Vector4> source,
Span<TPixel> destination)
=> PixelOperations<TPixel>.Instance.FromUnassociatedScaledVector4(configuration, source, destination);
/// <summary>
/// Blend 2 rows together.
/// </summary>

241
src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs

@ -0,0 +1,241 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Packed pixel type containing alpha and associated blue, green, and red components as 8-bit unsigned normalized values.
/// Components are stored in alpha, blue, green, and red order from least to most significant byte.
/// </summary>
/// <remarks>
/// Component, packed, and vector values use associated alpha representation.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
public partial struct Abgr32P : IPixel<Abgr32P>, IPackedVector<uint>
{
private const float ByteScale = 1F / byte.MaxValue;
private static readonly Vector4 Half = new(0.5F);
private static readonly Vector4 MaxBytes = new(byte.MaxValue);
/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public byte A;
/// <summary>
/// Gets or sets the associated blue component.
/// </summary>
public byte B;
/// <summary>
/// Gets or sets the associated green component.
/// </summary>
public byte G;
/// <summary>
/// Gets or sets the associated red component.
/// </summary>
public byte R;
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Abgr32P(byte r, byte g, byte b)
: this(r, g, b, byte.MaxValue)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Abgr32P(byte r, byte g, byte b, byte a)
{
this.A = a;
this.B = b;
this.G = g;
this.R = r;
}
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Abgr32P(float r, float g, float b)
: this(new Vector4(r, g, b, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Abgr32P(float r, float g, float b, float a)
: this(new Vector4(r, g, b, a))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Abgr32P(Vector3 vector)
: this(new Vector4(vector, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Abgr32P(Vector4 vector)
: this() => this = FromScaledVector4(vector);
/// <summary>
/// Initializes a new instance of the <see cref="Abgr32P"/> struct from a packed associated value.
/// </summary>
/// <param name="packed">The packed associated value.</param>
public Abgr32P(uint packed)
: this() => this.PackedValue = packed;
/// <inheritdoc />
public uint PackedValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
readonly get => Unsafe.As<Abgr32P, uint>(ref Unsafe.AsRef(in this));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Unsafe.As<Abgr32P, uint>(ref this) = value;
}
/// <summary>
/// Compares two <see cref="Abgr32P"/> values for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are equal.</returns>
public static bool operator ==(Abgr32P left, Abgr32P right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Abgr32P"/> values for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are not equal.</returns>
public static bool operator !=(Abgr32P left, Abgr32P right) => !left.Equals(right);
/// <inheritdoc />
public readonly Rgba32 ToRgba32()
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A));
/// <inheritdoc />
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale;
/// <inheritdoc />
public readonly Vector4 ToVector4() => this.ToScaledVector4();
/// <inheritdoc />
public static PixelTypeInfo GetPixelTypeInfo()
=> PixelTypeInfo.Create<Abgr32P>(
PixelComponentInfo.Create<Abgr32P>(4, 8, 8, 8, 8),
PixelColorType.Alpha | PixelColorType.BGR,
PixelAlphaRepresentation.Associated);
/// <inheritdoc />
public static PixelOperations<Abgr32P> CreatePixelOperations() => new PixelOperations();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Abgr32P FromScaledVector4(Vector4 source) => Pack(source);
/// <inheritdoc />
public static Abgr32P FromVector4(Vector4 source) => FromScaledVector4(source);
/// <inheritdoc />
public static Abgr32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Abgr32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override readonly bool Equals(object? obj) => obj is Abgr32P other && this.Equals(other);
/// <inheritdoc />
public readonly bool Equals(Abgr32P other) => this.PackedValue.Equals(other.PackedValue);
/// <inheritdoc />
public override readonly int GetHashCode() => this.PackedValue.GetHashCode();
/// <inheritdoc />
public override readonly string ToString() => $"Abgr32P({this.R}, {this.G}, {this.B}, {this.A})";
/// <summary>
/// Converts an unassociated scaled vector to associated representation.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated pixel.</returns>
private static Abgr32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Abgr32P Pack(Vector4 vector)
{
vector *= MaxBytes;
vector += Half;
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes);
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte();
return new Abgr32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12));
}
}

241
src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs

@ -0,0 +1,241 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Packed pixel type containing alpha and associated red, green, and blue components as 8-bit unsigned normalized values.
/// Components are stored in alpha, red, green, and blue order from least to most significant byte.
/// </summary>
/// <remarks>
/// Component, packed, and vector values use associated alpha representation.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
public partial struct Argb32P : IPixel<Argb32P>, IPackedVector<uint>
{
private const float ByteScale = 1F / byte.MaxValue;
private static readonly Vector4 Half = new(0.5F);
private static readonly Vector4 MaxBytes = new(byte.MaxValue);
/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public byte A;
/// <summary>
/// Gets or sets the associated red component.
/// </summary>
public byte R;
/// <summary>
/// Gets or sets the associated green component.
/// </summary>
public byte G;
/// <summary>
/// Gets or sets the associated blue component.
/// </summary>
public byte B;
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Argb32P(byte r, byte g, byte b)
: this(r, g, b, byte.MaxValue)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Argb32P(byte r, byte g, byte b, byte a)
{
this.A = a;
this.R = r;
this.G = g;
this.B = b;
}
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Argb32P(float r, float g, float b)
: this(new Vector4(r, g, b, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Argb32P(float r, float g, float b, float a)
: this(new Vector4(r, g, b, a))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Argb32P(Vector3 vector)
: this(new Vector4(vector, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Argb32P(Vector4 vector)
: this() => this = FromScaledVector4(vector);
/// <summary>
/// Initializes a new instance of the <see cref="Argb32P"/> struct from a packed associated value.
/// </summary>
/// <param name="packed">The packed associated value.</param>
public Argb32P(uint packed)
: this() => this.PackedValue = packed;
/// <inheritdoc />
public uint PackedValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
readonly get => Unsafe.As<Argb32P, uint>(ref Unsafe.AsRef(in this));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Unsafe.As<Argb32P, uint>(ref this) = value;
}
/// <summary>
/// Compares two <see cref="Argb32P"/> values for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are equal.</returns>
public static bool operator ==(Argb32P left, Argb32P right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Argb32P"/> values for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are not equal.</returns>
public static bool operator !=(Argb32P left, Argb32P right) => !left.Equals(right);
/// <inheritdoc />
public readonly Rgba32 ToRgba32()
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A));
/// <inheritdoc />
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale;
/// <inheritdoc />
public readonly Vector4 ToVector4() => this.ToScaledVector4();
/// <inheritdoc />
public static PixelTypeInfo GetPixelTypeInfo()
=> PixelTypeInfo.Create<Argb32P>(
PixelComponentInfo.Create<Argb32P>(4, 8, 8, 8, 8),
PixelColorType.Alpha | PixelColorType.RGB,
PixelAlphaRepresentation.Associated);
/// <inheritdoc />
public static PixelOperations<Argb32P> CreatePixelOperations() => new PixelOperations();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Argb32P FromScaledVector4(Vector4 source) => Pack(source);
/// <inheritdoc />
public static Argb32P FromVector4(Vector4 source) => FromScaledVector4(source);
/// <inheritdoc />
public static Argb32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Argb32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override readonly bool Equals(object? obj) => obj is Argb32P other && this.Equals(other);
/// <inheritdoc />
public readonly bool Equals(Argb32P other) => this.PackedValue.Equals(other.PackedValue);
/// <inheritdoc />
public override readonly int GetHashCode() => this.PackedValue.GetHashCode();
/// <inheritdoc />
public override readonly string ToString() => $"Argb32P({this.R}, {this.G}, {this.B}, {this.A})";
/// <summary>
/// Converts an unassociated scaled vector to associated representation.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated pixel.</returns>
private static Argb32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Argb32P Pack(Vector4 vector)
{
vector *= MaxBytes;
vector += Half;
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes);
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte();
return new Argb32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12));
}
}

241
src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs

@ -0,0 +1,241 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Packed pixel type containing associated blue, green, red, and alpha components as 8-bit unsigned normalized values.
/// Components are stored in blue, green, red, and alpha order from least to most significant byte.
/// </summary>
/// <remarks>
/// Component, packed, and vector values use associated alpha representation.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
public partial struct Bgra32P : IPixel<Bgra32P>, IPackedVector<uint>
{
private const float ByteScale = 1F / byte.MaxValue;
private static readonly Vector4 Half = new(0.5F);
private static readonly Vector4 MaxBytes = new(byte.MaxValue);
/// <summary>
/// Gets or sets the associated blue component.
/// </summary>
public byte B;
/// <summary>
/// Gets or sets the associated green component.
/// </summary>
public byte G;
/// <summary>
/// Gets or sets the associated red component.
/// </summary>
public byte R;
/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public byte A;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Bgra32P(byte r, byte g, byte b)
: this(r, g, b, byte.MaxValue)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Bgra32P(byte r, byte g, byte b, byte a)
{
this.B = b;
this.G = g;
this.R = r;
this.A = a;
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Bgra32P(float r, float g, float b)
: this(new Vector4(r, g, b, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Bgra32P(float r, float g, float b, float a)
: this(new Vector4(r, g, b, a))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Bgra32P(Vector3 vector)
: this(new Vector4(vector, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Bgra32P(Vector4 vector)
: this() => this = FromScaledVector4(vector);
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32P"/> struct from a packed associated value.
/// </summary>
/// <param name="packed">The packed associated value.</param>
public Bgra32P(uint packed)
: this() => this.PackedValue = packed;
/// <inheritdoc />
public uint PackedValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
readonly get => Unsafe.As<Bgra32P, uint>(ref Unsafe.AsRef(in this));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Unsafe.As<Bgra32P, uint>(ref this) = value;
}
/// <summary>
/// Compares two <see cref="Bgra32P"/> values for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are equal.</returns>
public static bool operator ==(Bgra32P left, Bgra32P right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Bgra32P"/> values for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are not equal.</returns>
public static bool operator !=(Bgra32P left, Bgra32P right) => !left.Equals(right);
/// <inheritdoc />
public readonly Rgba32 ToRgba32()
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A));
/// <inheritdoc />
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale;
/// <inheritdoc />
public readonly Vector4 ToVector4() => this.ToScaledVector4();
/// <inheritdoc />
public static PixelTypeInfo GetPixelTypeInfo()
=> PixelTypeInfo.Create<Bgra32P>(
PixelComponentInfo.Create<Bgra32P>(4, 8, 8, 8, 8),
PixelColorType.BGR | PixelColorType.Alpha,
PixelAlphaRepresentation.Associated);
/// <inheritdoc />
public static PixelOperations<Bgra32P> CreatePixelOperations() => new PixelOperations();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Bgra32P FromScaledVector4(Vector4 source) => Pack(source);
/// <inheritdoc />
public static Bgra32P FromVector4(Vector4 source) => FromScaledVector4(source);
/// <inheritdoc />
public static Bgra32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Bgra32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override readonly bool Equals(object? obj) => obj is Bgra32P other && this.Equals(other);
/// <inheritdoc />
public readonly bool Equals(Bgra32P other) => this.PackedValue.Equals(other.PackedValue);
/// <inheritdoc />
public override readonly int GetHashCode() => this.PackedValue.GetHashCode();
/// <inheritdoc />
public override readonly string ToString() => $"Bgra32P({this.R}, {this.G}, {this.B}, {this.A})";
/// <summary>
/// Converts an unassociated scaled vector to associated representation.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated pixel.</returns>
private static Bgra32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Bgra32P Pack(Vector4 vector)
{
vector *= MaxBytes;
vector += Half;
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes);
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte();
return new Bgra32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12));
}
}

243
src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs

@ -0,0 +1,243 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Packed pixel type containing four associated 16-bit floating-point values.
/// </summary>
/// <remarks>
/// Packed and vector values use associated alpha representation.
/// </remarks>
public partial struct HalfVector4P : IPixel<HalfVector4P>, IPackedVector<ulong>
{
/// <summary>
/// Initializes a new instance of the <see cref="HalfVector4P"/> struct.
/// </summary>
/// <param name="x">The associated x-component.</param>
/// <param name="y">The associated y-component.</param>
/// <param name="z">The associated z-component.</param>
/// <param name="w">The alpha component.</param>
public HalfVector4P(float x, float y, float z, float w)
: this(new Vector4(x, y, z, w))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HalfVector4P"/> struct.
/// </summary>
/// <param name="vector">The vector containing the associated component values.</param>
public HalfVector4P(Vector4 vector) => this.PackedValue = Pack(vector);
/// <inheritdoc />
public ulong PackedValue { get; set; }
/// <summary>
/// Compares two <see cref="HalfVector4P"/> values for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are equal.</returns>
public static bool operator ==(HalfVector4P left, HalfVector4P right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="HalfVector4P"/> values for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are not equal.</returns>
public static bool operator !=(HalfVector4P left, HalfVector4P right) => !left.Equals(right);
/// <inheritdoc />
public readonly Rgba32 ToRgba32()
{
Vector4 vector = this.ToScaledVector4();
Numerics.UnPremultiply(ref vector);
return Rgba32.FromScaledVector4(vector);
}
/// <inheritdoc />
public readonly Vector4 ToScaledVector4()
{
Vector4 scaled = this.ToVector4();
scaled += Vector4.One;
scaled /= 2F;
return scaled;
}
/// <inheritdoc />
public readonly Vector4 ToVector4() => new(
HalfTypeHelper.Unpack((ushort)this.PackedValue),
HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)),
HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)),
HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30)));
/// <inheritdoc />
public static PixelTypeInfo GetPixelTypeInfo()
=> PixelTypeInfo.Create<HalfVector4P>(
PixelComponentInfo.Create<HalfVector4P>(4, 16, 16, 16, 16),
PixelColorType.RGB | PixelColorType.Alpha,
PixelAlphaRepresentation.Associated);
/// <inheritdoc />
public static PixelOperations<HalfVector4P> CreatePixelOperations() => new PixelOperations();
/// <inheritdoc />
public static HalfVector4P FromScaledVector4(Vector4 source)
{
source *= 2F;
source -= Vector4.One;
return FromVector4(source);
}
/// <inheritdoc />
public static HalfVector4P FromVector4(Vector4 source) => new() { PackedValue = Pack(source) };
/// <inheritdoc />
public static HalfVector4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static HalfVector4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override readonly bool Equals(object? obj) => obj is HalfVector4P other && this.Equals(other);
/// <inheritdoc />
public readonly bool Equals(HalfVector4P other) => this.PackedValue.Equals(other.PackedValue);
/// <inheritdoc />
public override readonly int GetHashCode() => this.PackedValue.GetHashCode();
/// <inheritdoc />
public override readonly string ToString()
{
Vector4 vector = this.ToVector4();
return FormattableString.Invariant($"HalfVector4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})");
}
/// <summary>
/// Converts an unassociated scaled vector to associated representation.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated pixel.</returns>
private static HalfVector4P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Associate(source));
/// <summary>
/// Converts an unassociated scaled vector to the associated representation of a half-precision destination.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Associate(Vector4 source)
{
// Quantize alpha through the destination's native half representation before RGB is associated with it.
float nativeAlpha = (source.W * 2F) - 1F;
source.W = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F;
Numerics.Premultiply(ref source);
return source;
}
/// <summary>
/// Converts unassociated scaled vectors to the associated representation of a half-precision destination.
/// </summary>
/// <param name="source">The vectors to convert in place.</param>
private static void Associate(Span<Vector4> source)
{
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i));
}
}
/// <summary>
/// Reassociates a scaled vector with the alpha value the destination stores.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The reassociated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Reassociate(Vector4 source)
{
float alpha = source.W;
if (alpha == 0)
{
return Vector4.Zero;
}
float nativeAlpha = (alpha * 2F) - 1F;
float storedAlpha = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F;
// Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization.
source *= storedAlpha / alpha;
source.W = storedAlpha;
return source;
}
/// <summary>
/// Reassociates scaled vectors with the alpha values the destination stores.
/// </summary>
/// <param name="source">The vectors to convert in place.</param>
private static void Reassociate(Span<Vector4> source)
{
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i));
}
}
/// <summary>
/// Packs native half-precision components into a 64-bit value.
/// </summary>
/// <param name="vector">The native component values.</param>
/// <returns>The packed value.</returns>
private static ulong Pack(Vector4 vector)
{
ulong x = HalfTypeHelper.Pack(vector.X);
ulong y = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10;
ulong z = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20;
ulong w = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30;
return x | y | z | w;
}
}

276
src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs

@ -0,0 +1,276 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1.
/// </summary>
/// <remarks>
/// Packed and vector values use associated alpha representation.
/// </remarks>
public partial struct NormalizedByte4P : IPixel<NormalizedByte4P>, IPackedVector<uint>
{
private const float MaxPos = 127F;
private const float ScaledMagnitude = MaxPos * 2F;
private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4();
private static readonly Vector4 MinusOne = Vector128.Create(-1F).AsVector4();
/// <summary>
/// Initializes a new instance of the <see cref="NormalizedByte4P"/> struct.
/// </summary>
/// <param name="x">The associated x-component.</param>
/// <param name="y">The associated y-component.</param>
/// <param name="z">The associated z-component.</param>
/// <param name="w">The alpha component.</param>
public NormalizedByte4P(float x, float y, float z, float w)
: this(new Vector4(x, y, z, w))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NormalizedByte4P"/> struct.
/// </summary>
/// <param name="vector">The vector containing the associated component values.</param>
public NormalizedByte4P(Vector4 vector) => this.PackedValue = Pack(vector);
/// <inheritdoc />
public uint PackedValue { get; set; }
/// <summary>
/// Compares two <see cref="NormalizedByte4P"/> values for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are equal.</returns>
public static bool operator ==(NormalizedByte4P left, NormalizedByte4P right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="NormalizedByte4P"/> values for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are not equal.</returns>
public static bool operator !=(NormalizedByte4P left, NormalizedByte4P right) => !left.Equals(right);
/// <inheritdoc />
public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(this));
/// <inheritdoc />
public readonly Vector4 ToScaledVector4()
{
Vector4 scaled = this.ToVector4();
scaled += Vector4.One;
scaled /= 2F;
return scaled;
}
/// <inheritdoc />
public readonly Vector4 ToVector4() => new(
(sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos,
(sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos,
(sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos,
(sbyte)((this.PackedValue >> 24) & 0xFF) / MaxPos);
/// <inheritdoc />
public static PixelTypeInfo GetPixelTypeInfo()
=> PixelTypeInfo.Create<NormalizedByte4P>(
PixelComponentInfo.Create<NormalizedByte4P>(4, 8, 8, 8, 8),
PixelColorType.RGB | PixelColorType.Alpha,
PixelAlphaRepresentation.Associated);
/// <inheritdoc />
public static PixelOperations<NormalizedByte4P> CreatePixelOperations() => new PixelOperations();
/// <inheritdoc />
public static NormalizedByte4P FromScaledVector4(Vector4 source)
{
source *= 2F;
source -= Vector4.One;
return FromVector4(source);
}
/// <inheritdoc />
public static NormalizedByte4P FromVector4(Vector4 source) => new() { PackedValue = Pack(source) };
/// <inheritdoc />
public static NormalizedByte4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static NormalizedByte4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override readonly bool Equals(object? obj) => obj is NormalizedByte4P other && this.Equals(other);
/// <inheritdoc />
public readonly bool Equals(NormalizedByte4P other) => this.PackedValue.Equals(other.PackedValue);
/// <inheritdoc />
public override readonly int GetHashCode() => this.PackedValue.GetHashCode();
/// <inheritdoc />
public override readonly string ToString()
{
Vector4 vector = this.ToVector4();
return FormattableString.Invariant($"NormalizedByte4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})");
}
/// <summary>
/// Converts an unassociated scaled vector to associated representation.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated pixel.</returns>
private static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source)
{
return FromScaledVector4(Associate(source));
}
/// <summary>
/// Converts an unassociated scaled vector to the associated representation of a signed-normalized-byte destination.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Associate(Vector4 source)
{
// Reproduce the signed-normalized packer's alpha quantization, then associate RGB with the exact alpha that will be stored.
float nativeAlpha = Numerics.Clamp((source.W * 2F) - 1F, -1F, 1F);
float storedAlpha = MathF.Round(nativeAlpha * MaxPos);
source.W = (storedAlpha + MaxPos) / ScaledMagnitude;
Numerics.Premultiply(ref source);
return source;
}
/// <summary>
/// Converts the stored associated components to an unassociated scaled vector.
/// </summary>
/// <param name="source">The associated pixel.</param>
/// <returns>The unassociated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source)
{
// Offset signed storage into exact nonnegative byte magnitudes before division so the quotient retains the destination's byte-rounding midpoint.
Vector4 vector = new(
(sbyte)(source.PackedValue >> 0) + MaxPos,
(sbyte)(source.PackedValue >> 8) + MaxPos,
(sbyte)(source.PackedValue >> 16) + MaxPos,
(sbyte)(source.PackedValue >> 24) + MaxPos);
if (vector.W == 0F)
{
// Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case.
return vector / ScaledMagnitude;
}
Numerics.UnPremultiply(ref vector);
vector.W /= ScaledMagnitude;
return vector;
}
/// <summary>
/// Converts unassociated scaled vectors to the associated representation of a signed-normalized-byte destination.
/// </summary>
/// <param name="source">The vectors to convert in place.</param>
private static void Associate(Span<Vector4> source)
{
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i));
}
}
/// <summary>
/// Reassociates a scaled vector with the alpha value the destination stores.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The reassociated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Reassociate(Vector4 source)
{
float alpha = source.W;
if (alpha == 0)
{
return Vector4.Zero;
}
float nativeAlpha = Numerics.Clamp((alpha * 2F) - 1F, -1F, 1F);
float storedAlpha = (MathF.Round(nativeAlpha * MaxPos) + MaxPos) / ScaledMagnitude;
// Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization.
source *= storedAlpha / alpha;
source.W = storedAlpha;
return source;
}
/// <summary>
/// Reassociates scaled vectors with the alpha values the destination stores.
/// </summary>
/// <param name="source">The vectors to convert in place.</param>
private static void Reassociate(Span<Vector4> source)
{
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i));
}
}
/// <summary>
/// Packs native signed normalized components into a 32-bit value.
/// </summary>
/// <param name="vector">The native component values.</param>
/// <returns>The packed value.</returns>
private static uint Pack(Vector4 vector)
{
vector = Numerics.Clamp(vector, MinusOne, Vector4.One) * Half;
uint byte4 = ((uint)Convert.ToInt16(MathF.Round(vector.X)) & 0xFF) << 0;
uint byte3 = ((uint)Convert.ToInt16(MathF.Round(vector.Y)) & 0xFF) << 8;
uint byte2 = ((uint)Convert.ToInt16(MathF.Round(vector.Z)) & 0xFF) << 16;
uint byte1 = ((uint)Convert.ToInt16(MathF.Round(vector.W)) & 0xFF) << 24;
return byte4 | byte3 | byte2 | byte1;
}
}

66
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
/// Provides optimized overrides for bulk operations.
/// </content>
public partial struct Abgr32P
{
/// <summary>
/// Provides optimized bulk operations for <see cref="Abgr32P"/>.
/// </summary>
internal class PixelOperations : AssociatedAlphaPixelOperations<Abgr32P>
{
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(Abgr32P source)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A);
/// <inheritdoc />
internal override Abgr32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
/// <inheritdoc />
public override Abgr32P FromAssociatedScaledVector4(Vector4 source)
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToAbgr32P(source);
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Abgr32P> source, Span<Vector4> destination)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination);
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Abgr32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.Associate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Abgr32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination);
}
/// <inheritdoc />
public override void ToVector4(
Configuration configuration,
ReadOnlySpan<Abgr32P> source,
Span<Vector4> destinationVectors,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers);
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
Span<Vector4> sourceVectors,
Span<Abgr32P> destination,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers);
}
}

66
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
/// Provides optimized overrides for bulk operations.
/// </content>
public partial struct Argb32P
{
/// <summary>
/// Provides optimized bulk operations for <see cref="Argb32P"/>.
/// </summary>
internal class PixelOperations : AssociatedAlphaPixelOperations<Argb32P>
{
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(Argb32P source)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A);
/// <inheritdoc />
internal override Argb32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
/// <inheritdoc />
public override Argb32P FromAssociatedScaledVector4(Vector4 source)
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToArgb32P(source);
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Argb32P> source, Span<Vector4> destination)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination);
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Argb32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.Associate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Argb32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination);
}
/// <inheritdoc />
public override void ToVector4(
Configuration configuration,
ReadOnlySpan<Argb32P> source,
Span<Vector4> destinationVectors,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers);
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
Span<Vector4> sourceVectors,
Span<Argb32P> destination,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers);
}
}

66
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
/// Provides optimized overrides for bulk operations.
/// </content>
public partial struct Bgra32P
{
/// <summary>
/// Provides optimized bulk operations for <see cref="Bgra32P"/>.
/// </summary>
internal class PixelOperations : AssociatedAlphaPixelOperations<Bgra32P>
{
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(Bgra32P source)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A);
/// <inheritdoc />
internal override Bgra32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
/// <inheritdoc />
public override Bgra32P FromAssociatedScaledVector4(Vector4 source)
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToBgra32P(source);
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Bgra32P> source, Span<Vector4> destination)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination);
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Bgra32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.Associate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Bgra32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination);
}
/// <inheritdoc />
public override void ToVector4(
Configuration configuration,
ReadOnlySpan<Bgra32P> source,
Span<Vector4> destinationVectors,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers);
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
Span<Vector4> sourceVectors,
Span<Bgra32P> destination,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers);
}
}

3
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs

@ -7,7 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
@ -45,7 +44,7 @@ public partial struct Bgr24
Span<Bgr24> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply));
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale));
}
/// <inheritdoc />

3
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs

@ -7,7 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
@ -45,7 +44,7 @@ public partial struct Rgb24
Span<Rgb24> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply));
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale));
}
/// <inheritdoc />

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

@ -147,15 +147,19 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType, bool hasAlpha)
{
string removeTheseModifiers = "PixelConversionModifiers.Scale";
string removeFromModifiers = "PixelConversionModifiers.Scale";
string removeToModifiers = "PixelConversionModifiers.Scale";
if (!hasAlpha)
{
removeTheseModifiers += " | PixelConversionModifiers.Premultiply";
// Premultiplication cannot change an alpha-less source, but premultiplied input
// must still be unassociated before its alpha channel is discarded.
removeToModifiers += " | PixelConversionModifiers.Premultiply";
}
if (hasAlpha)
{
GenerateRgba32Compatible32BitVector4ConversionMethods(pixelType, removeTheseModifiers);
GenerateRgba32Compatible32BitVector4ConversionMethods(pixelType, removeFromModifiers);
return;
}
#>
@ -167,7 +171,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
Span<<#=pixelType#>> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(<#=removeTheseModifiers#>));
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(<#=removeFromModifiers#>));
}
/// <inheritdoc />
@ -177,7 +181,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
Span<Vector4> destination,
PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeTheseModifiers#>));
Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeToModifiers#>));
}
<#+
}

57
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs

@ -0,0 +1,57 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
/// Provides bulk operations.
/// </content>
public partial struct HalfVector4P
{
/// <summary>
/// Provides bulk operations for <see cref="HalfVector4P"/>.
/// </summary>
internal class PixelOperations : AssociatedAlphaPixelOperations<HalfVector4P>
{
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(HalfVector4P source)
{
Vector4 vector = source.ToScaledVector4();
Numerics.UnPremultiply(ref vector);
return vector;
}
/// <inheritdoc />
internal override HalfVector4P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Associate(source));
/// <inheritdoc />
public override HalfVector4P FromAssociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Reassociate(source));
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<HalfVector4P> source, Span<Vector4> destination)
{
this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale);
Numerics.UnPremultiply(destination[..source.Length]);
}
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<HalfVector4P> destination)
{
source = source[..destination.Length];
Associate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<HalfVector4P> destination)
{
source = source[..destination.Length];
Reassociate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
}
}

62
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
/// Provides bulk operations.
/// </content>
public partial struct NormalizedByte4P
{
/// <summary>
/// Provides bulk operations for <see cref="NormalizedByte4P"/>.
/// </summary>
internal class PixelOperations : AssociatedAlphaPixelOperations<NormalizedByte4P>
{
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source)
=> NormalizedByte4P.ToUnassociatedScaledVector4(source);
/// <inheritdoc />
internal override NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Associate(source));
/// <inheritdoc />
public override NormalizedByte4P FromAssociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Reassociate(source));
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<NormalizedByte4P> source, Span<Vector4> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
ref NormalizedByte4P sourceBase = ref MemoryMarshal.GetReference(source);
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref destinationBase, i) = NormalizedByte4P.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i));
}
}
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<NormalizedByte4P> destination)
{
source = source[..destination.Length];
Associate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<NormalizedByte4P> destination)
{
source = source[..destination.Length];
Reassociate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
}
}

66
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <content>
/// Provides optimized overrides for bulk operations.
/// </content>
public partial struct Rgba32P
{
/// <summary>
/// Provides optimized bulk operations for <see cref="Rgba32P"/>.
/// </summary>
internal class PixelOperations : AssociatedAlphaPixelOperations<Rgba32P>
{
/// <inheritdoc />
internal override Vector4 ToUnassociatedScaledVector4(Rgba32P source)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A);
/// <inheritdoc />
internal override Rgba32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
/// <inheritdoc />
public override Rgba32P FromAssociatedScaledVector4(Vector4 source)
=> Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToRgba32P(source);
/// <inheritdoc />
internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan<Rgba32P> source, Span<Vector4> destination)
=> Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination);
/// <inheritdoc />
internal override void FromUnassociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Rgba32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.Associate(source);
this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
}
/// <inheritdoc />
public override void FromAssociatedScaledVector4(Configuration configuration, Span<Vector4> source, Span<Rgba32P> destination)
{
source = source[..destination.Length];
Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination);
}
/// <inheritdoc />
public override void ToVector4(
Configuration configuration,
ReadOnlySpan<Rgba32P> source,
Span<Vector4> destinationVectors,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers);
/// <inheritdoc />
public override void FromVector4Destructive(
Configuration configuration,
Span<Vector4> sourceVectors,
Span<Rgba32P> destination,
PixelConversionModifiers modifiers)
=> Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers);
}
}

5
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs

@ -25,7 +25,10 @@ public partial struct RgbaVector
{
Span<Vector4> destinationVectors = MemoryMarshal.Cast<RgbaVector, Vector4>(destinationPixels);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale);
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, sourcePixels, destinationVectors);
// RgbaVector.FromScaledVector4 clamps scaled input, so the optimized bulk path must preserve that behavior after unassociating.
Numerics.Clamp(MemoryMarshal.Cast<Vector4, float>(destinationVectors), 0F, 1F);
}
/// <inheritdoc />

241
src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs

@ -0,0 +1,241 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Packed pixel type containing associated red, green, blue, and alpha components as 8-bit unsigned normalized values.
/// Components are stored in red, green, blue, and alpha order from least to most significant byte.
/// </summary>
/// <remarks>
/// Component, packed, and vector values use associated alpha representation.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
public partial struct Rgba32P : IPixel<Rgba32P>, IPackedVector<uint>
{
private const float ByteScale = 1F / byte.MaxValue;
private static readonly Vector4 Half = new(0.5F);
private static readonly Vector4 MaxBytes = new(byte.MaxValue);
/// <summary>
/// Gets or sets the associated red component.
/// </summary>
public byte R;
/// <summary>
/// Gets or sets the associated green component.
/// </summary>
public byte G;
/// <summary>
/// Gets or sets the associated blue component.
/// </summary>
public byte B;
/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public byte A;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Rgba32P(byte r, byte g, byte b)
: this(r, g, b, byte.MaxValue)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Rgba32P(byte r, byte g, byte b, byte a)
{
this.R = r;
this.G = g;
this.B = b;
this.A = a;
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
public Rgba32P(float r, float g, float b)
: this(new Vector4(r, g, b, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from associated components.
/// </summary>
/// <param name="r">The associated red component.</param>
/// <param name="g">The associated green component.</param>
/// <param name="b">The associated blue component.</param>
/// <param name="a">The alpha component.</param>
public Rgba32P(float r, float g, float b, float a)
: this(new Vector4(r, g, b, a))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Rgba32P(Vector3 vector)
: this(new Vector4(vector, 1F))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from an associated vector.
/// </summary>
/// <param name="vector">The associated vector.</param>
public Rgba32P(Vector4 vector)
: this() => this = FromScaledVector4(vector);
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32P"/> struct from a packed associated value.
/// </summary>
/// <param name="packed">The packed associated value.</param>
public Rgba32P(uint packed)
: this() => this.PackedValue = packed;
/// <inheritdoc />
public uint PackedValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
readonly get => Unsafe.As<Rgba32P, uint>(ref Unsafe.AsRef(in this));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Unsafe.As<Rgba32P, uint>(ref this) = value;
}
/// <summary>
/// Compares two <see cref="Rgba32P"/> values for equality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are equal.</returns>
public static bool operator ==(Rgba32P left, Rgba32P right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Rgba32P"/> values for inequality.
/// </summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns><see langword="true"/> when the values are not equal.</returns>
public static bool operator !=(Rgba32P left, Rgba32P right) => !left.Equals(right);
/// <inheritdoc />
public readonly Rgba32 ToRgba32()
=> Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A));
/// <inheritdoc />
public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) * ByteScale;
/// <inheritdoc />
public readonly Vector4 ToVector4() => this.ToScaledVector4();
/// <inheritdoc />
public static PixelTypeInfo GetPixelTypeInfo()
=> PixelTypeInfo.Create<Rgba32P>(
PixelComponentInfo.Create<Rgba32P>(4, 8, 8, 8, 8),
PixelColorType.RGB | PixelColorType.Alpha,
PixelAlphaRepresentation.Associated);
/// <inheritdoc />
public static PixelOperations<Rgba32P> CreatePixelOperations() => new PixelOperations();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgba32P FromScaledVector4(Vector4 source) => Pack(source);
/// <inheritdoc />
public static Rgba32P FromVector4(Vector4 source) => FromScaledVector4(source);
/// <inheritdoc />
public static Rgba32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public static Rgba32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override readonly bool Equals(object? obj) => obj is Rgba32P other && this.Equals(other);
/// <inheritdoc />
public readonly bool Equals(Rgba32P other) => this.PackedValue.Equals(other.PackedValue);
/// <inheritdoc />
public override readonly int GetHashCode() => this.PackedValue.GetHashCode();
/// <inheritdoc />
public override readonly string ToString() => $"Rgba32P({this.R}, {this.G}, {this.B}, {this.A})";
/// <summary>
/// Converts an unassociated scaled vector to associated representation.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated pixel.</returns>
private static Rgba32P FromUnassociatedScaledVector4(Vector4 source)
=> FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Rgba32P Pack(Vector4 vector)
{
vector *= MaxBytes;
vector += Half;
vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes);
Vector128<byte> result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte();
return new Rgba32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12));
}
}

65
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -26,6 +26,59 @@ public partial class PixelOperations<TPixel>
public static PixelOperations<TPixel> Instance => LazyInstance.Value;
#pragma warning restore CA1000 // Do not declare static members on generic types
/// <summary>
/// Converts a pixel to the unassociated scaled vector representation used by color operations.
/// </summary>
/// <param name="source">The source pixel.</param>
/// <returns>The unassociated scaled vector.</returns>
internal virtual Vector4 ToUnassociatedScaledVector4(TPixel source) => source.ToScaledVector4();
/// <summary>
/// Converts an unassociated scaled vector to a pixel.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The converted pixel.</returns>
internal virtual TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(source);
/// <summary>
/// Converts pixels to the unassociated scaled vector representation used by color operations.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
internal virtual void ToUnassociatedScaledVector4(
Configuration configuration,
ReadOnlySpan<TPixel> source,
Span<Vector4> destination)
=> this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale);
/// <summary>
/// Converts pixels to associated scaled vectors.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
internal virtual void ToAssociatedScaledVector4(
Configuration configuration,
ReadOnlySpan<TPixel> source,
Span<Vector4> destination)
{
this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale);
Numerics.Premultiply(destination[..source.Length]);
}
/// <summary>
/// Converts unassociated scaled vectors to pixels.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
internal virtual void FromUnassociatedScaledVector4(
Configuration configuration,
Span<Vector4> source,
Span<TPixel> destination)
=> this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale);
/// <summary>
/// Gets the pixel type info for the given <typeparamref name="TPixel"/>.
/// </summary>
@ -121,12 +174,13 @@ public partial class PixelOperations<TPixel>
using IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(sliceLength);
Span<Vector4> vectorSpan = tempVectors.GetSpan();
for (int i = 0; i < numberOfSlices; i++)
{
int start = i * sliceLength;
ReadOnlySpan<TSourcePixel> s = source.Slice(start, sliceLength);
Span<TPixel> d = destination.Slice(start, sliceLength);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale);
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, s, vectorSpan);
this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale);
}
@ -137,7 +191,7 @@ public partial class PixelOperations<TPixel>
ReadOnlySpan<TSourcePixel> s = source[endOfCompleteSlices..];
Span<TPixel> d = destination[endOfCompleteSlices..];
vectorSpan = vectorSpan[..remainder];
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale);
PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(configuration, s, vectorSpan);
this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale);
}
}
@ -159,6 +213,13 @@ public partial class PixelOperations<TPixel>
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
if (typeof(TPixel) == typeof(TDestinationPixel))
{
// An identical destination stores the same representation, including associated alpha, so copying preserves every packed component without a lossy representation round trip.
MemoryMarshal.Cast<TPixel, TDestinationPixel>(source).CopyTo(destination);
return;
}
PixelOperations<TDestinationPixel>.Instance.From(configuration, source, destination);
}

595
src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs

@ -0,0 +1,595 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.PixelFormats.Utils;
/// <content>
/// Contains <see cref="AssociatedRgbaCompatible"/>.
/// </content>
internal static partial class Vector4Converters
{
/// <summary>
/// Provides efficient batched conversion for four-byte pixel types that store premultiplied alpha.
/// </summary>
public static class AssociatedRgbaCompatible
{
/// <summary>
/// Converts associated RGBA byte components to an unassociated scaled vector.
/// </summary>
/// <param name="red">The associated red component.</param>
/// <param name="green">The associated green component.</param>
/// <param name="blue">The associated blue component.</param>
/// <param name="alpha">The alpha component.</param>
/// <returns>The unassociated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 ToUnassociatedVector4(byte red, byte green, byte blue, byte alpha)
{
// Divide the original byte magnitudes before normalization so a floating-point intermediate cannot move an exact byte conversion across its rounding midpoint.
Vector4 vector = new(red, green, blue, alpha);
if (alpha == 0)
{
// Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case.
return vector / byte.MaxValue;
}
Numerics.UnPremultiply(ref vector);
vector.W /= byte.MaxValue;
return vector;
}
/// <summary>
/// Converts an unassociated scaled vector to the associated representation of an unsigned-byte destination.
/// </summary>
/// <param name="source">The unassociated scaled vector.</param>
/// <returns>The associated scaled vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 Associate(Vector4 source)
{
float storedAlpha = (byte)Numerics.Clamp((source.W * byte.MaxValue) + 0.5F, 0, byte.MaxValue);
// Associate in byte magnitude before normalization. Multiplying by a normalized alpha and later restoring byte magnitude can move an exact half-byte across its rounding boundary.
source *= storedAlpha;
source.W = storedAlpha;
return source / byte.MaxValue;
}
/// <summary>
/// Converts unassociated scaled vectors to the associated representation of an unsigned-byte destination.
/// </summary>
/// <param name="source">The vectors to convert in place.</param>
internal static void Associate(Span<Vector4> source)
{
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i));
}
}
/// <summary>
/// Converts an associated scaled vector to associated byte magnitudes using the alpha byte the destination stores.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The associated byte magnitudes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 ReassociateToByte(Vector4 source)
{
float alpha = source.W;
if (alpha == 0)
{
return Vector4.Zero;
}
float byteAlpha = alpha * byte.MaxValue;
float storedAlpha = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue);
if (byteAlpha == storedAlpha)
{
// Quantization leaves alpha unchanged, so RGB is already associated correctly. Avoiding division preserves exact byte midpoints produced by scaling stored components.
source *= byte.MaxValue;
source.W = storedAlpha;
return source;
}
// Recover straight RGB before multiplying by the stored alpha byte. Keeping the association in byte magnitude preserves exact half-byte rounding boundaries.
Numerics.UnPremultiply(ref source);
source *= storedAlpha;
source.W = storedAlpha;
return source;
}
/// <summary>
/// Converts associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores.
/// </summary>
/// <param name="source">The vectors to convert in place.</param>
private static void ReassociateToByte(Span<Vector4> source)
{
if (Avx512F.IsSupported)
{
ref Vector512<float> vectorSourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref Vector512<float> sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / 4u);
while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd))
{
vectorSourceBase = ReassociateToByte(vectorSourceBase);
vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1);
}
source = source[(source.Length & ~3)..];
}
else if (Avx.IsSupported)
{
ref Vector256<float> vectorSourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref Vector256<float> sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / 2u);
while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd))
{
vectorSourceBase = ReassociateToByte(vectorSourceBase);
vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1);
}
source = source[(source.Length & ~1)..];
}
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Unsafe.Add(ref sourceBase, i) = ReassociateToByte(Unsafe.Add(ref sourceBase, i));
}
}
/// <summary>
/// Converts four associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores.
/// </summary>
/// <param name="source">The associated scaled vectors.</param>
/// <returns>The associated byte magnitudes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> ReassociateToByte(Vector512<float> source)
{
Vector512<float> zero = Vector512<float>.Zero;
Vector512<float> byteMax = Vector512.Create((float)byte.MaxValue);
Vector512<float> alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11);
Vector512<float> byteAlpha = alpha * byteMax;
Vector512<float> storedAlpha = Vector512.Floor(Vector512.Min(Vector512.Max(byteAlpha + Vector512.Create(.5F), zero), byteMax));
Vector512<float> result = (source / alpha) * storedAlpha;
// Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints.
result = Vector512.ConditionalSelect(Vector512.Equals(byteAlpha, storedAlpha), source * byteMax, result);
Vector512<float> alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle();
result = Vector512.ConditionalSelect(alphaMask, storedAlpha, result);
return Vector512.ConditionalSelect(Vector512.Equals(alpha, zero), zero, result);
}
/// <summary>
/// Converts two associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores.
/// </summary>
/// <param name="source">The associated scaled vectors.</param>
/// <returns>The associated byte magnitudes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> ReassociateToByte(Vector256<float> source)
{
Vector256<float> zero = Vector256<float>.Zero;
Vector256<float> byteMax = Vector256.Create((float)byte.MaxValue);
Vector256<float> alpha = Avx.Permute(source, 0b_11_11_11_11);
Vector256<float> byteAlpha = alpha * byteMax;
Vector256<float> storedAlpha = Avx.Floor(Avx.Min(Avx.Max(byteAlpha + Vector256.Create(.5F), zero), byteMax));
Vector256<float> result = (source / alpha) * storedAlpha;
// Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints.
result = Avx.BlendVariable(result, source * byteMax, Avx.CompareEqual(byteAlpha, storedAlpha));
result = Avx.Blend(result, storedAlpha, 0b_1000_1000);
return Avx.BlendVariable(result, zero, Avx.CompareEqual(alpha, zero));
}
/// <summary>
/// Converts an associated scaled vector to an <see cref="Rgba32P"/> pixel.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The associated pixel.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Rgba32P FromAssociatedVector4ToRgba32P(Vector4 source)
{
source = ReassociateToByte(source);
return new Rgba32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W));
}
/// <summary>
/// Converts an associated scaled vector to a <see cref="Bgra32P"/> pixel.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The associated pixel.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Bgra32P FromAssociatedVector4ToBgra32P(Vector4 source)
{
source = ReassociateToByte(source);
return new Bgra32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W));
}
/// <summary>
/// Converts an associated scaled vector to an <see cref="Argb32P"/> pixel.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The associated pixel.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Argb32P FromAssociatedVector4ToArgb32P(Vector4 source)
{
source = ReassociateToByte(source);
return new Argb32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W));
}
/// <summary>
/// Converts an associated scaled vector to an <see cref="Abgr32P"/> pixel.
/// </summary>
/// <param name="source">The associated scaled vector.</param>
/// <returns>The associated pixel.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Abgr32P FromAssociatedVector4ToAbgr32P(Vector4 source)
{
source = ReassociateToByte(source);
return new Abgr32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W));
}
/// <summary>
/// Converts premultiplied RGBA pixels to vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ToVector4(
ReadOnlySpan<Rgba32P> source,
Span<Vector4> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32P, byte>(source), MemoryMarshal.Cast<Vector4, float>(destination));
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply));
}
/// <summary>
/// Converts premultiplied RGBA pixels to unassociated scaled vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
internal static void ToUnassociatedVector4(ReadOnlySpan<Rgba32P> source, Span<Vector4> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
ref Rgba32P sourceBase = ref MemoryMarshal.GetReference(source);
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Rgba32P pixel = Unsafe.Add(ref sourceBase, i);
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A);
}
}
/// <summary>
/// Converts vectors to premultiplied RGBA pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void FromVector4(
Span<Vector4> source,
Span<Rgba32P> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply));
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), MemoryMarshal.Cast<Rgba32P, byte>(destination));
}
/// <summary>
/// Converts associated scaled vectors to premultiplied RGBA pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Rgba32P> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ReassociateToByte(source);
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), MemoryMarshal.Cast<Rgba32P, byte>(destination));
}
/// <summary>
/// Converts premultiplied BGRA pixels to vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ToVector4(
ReadOnlySpan<Bgra32P> source,
Span<Vector4> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
if (source.IsEmpty)
{
return;
}
// ByteToNormalizedFloat preserves component order, so the packed BGRA bytes must first be shuffled to RGBA.
// Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation.
// The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed.
int lastIndex = source.Length - 1;
Span<Rgba32> temporary = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * source.Length) + 1, lastIndex);
PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast<Bgra32P, byte>(source[..lastIndex]), MemoryMarshal.Cast<Rgba32, byte>(temporary));
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32, byte>(temporary), MemoryMarshal.Cast<Vector4, float>(destination[..lastIndex]));
destination[lastIndex] = source[lastIndex].ToVector4();
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply));
}
/// <summary>
/// Converts premultiplied BGRA pixels to unassociated scaled vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
internal static void ToUnassociatedVector4(ReadOnlySpan<Bgra32P> source, Span<Vector4> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
ref Bgra32P sourceBase = ref MemoryMarshal.GetReference(source);
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Bgra32P pixel = Unsafe.Add(ref sourceBase, i);
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A);
}
}
/// <summary>
/// Converts vectors to premultiplied BGRA pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void FromVector4(
Span<Vector4> source,
Span<Bgra32P> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply));
// NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer.
Span<byte> destinationBytes = MemoryMarshal.Cast<Bgra32P, byte>(destination);
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes);
PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes);
}
/// <summary>
/// Converts associated scaled vectors to premultiplied BGRA pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Bgra32P> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ReassociateToByte(source);
Span<byte> destinationBytes = MemoryMarshal.Cast<Bgra32P, byte>(destination);
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes);
PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes);
}
/// <summary>
/// Converts premultiplied ARGB pixels to vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ToVector4(
ReadOnlySpan<Argb32P> source,
Span<Vector4> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
if (source.IsEmpty)
{
return;
}
// ByteToNormalizedFloat preserves component order, so the packed ARGB bytes must first be shuffled to RGBA.
// Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation.
// The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed.
int lastIndex = source.Length - 1;
Span<Rgba32> temporary = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * source.Length) + 1, lastIndex);
PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast<Argb32P, byte>(source[..lastIndex]), MemoryMarshal.Cast<Rgba32, byte>(temporary));
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32, byte>(temporary), MemoryMarshal.Cast<Vector4, float>(destination[..lastIndex]));
destination[lastIndex] = source[lastIndex].ToVector4();
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply));
}
/// <summary>
/// Converts premultiplied ARGB pixels to unassociated scaled vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
internal static void ToUnassociatedVector4(ReadOnlySpan<Argb32P> source, Span<Vector4> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
ref Argb32P sourceBase = ref MemoryMarshal.GetReference(source);
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Argb32P pixel = Unsafe.Add(ref sourceBase, i);
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A);
}
}
/// <summary>
/// Converts vectors to premultiplied ARGB pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void FromVector4(
Span<Vector4> source,
Span<Argb32P> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply));
// NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer.
Span<byte> destinationBytes = MemoryMarshal.Cast<Argb32P, byte>(destination);
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes);
PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes);
}
/// <summary>
/// Converts associated scaled vectors to premultiplied ARGB pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Argb32P> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ReassociateToByte(source);
Span<byte> destinationBytes = MemoryMarshal.Cast<Argb32P, byte>(destination);
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes);
PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes);
}
/// <summary>
/// Converts premultiplied ABGR pixels to vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ToVector4(
ReadOnlySpan<Abgr32P> source,
Span<Vector4> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
if (source.IsEmpty)
{
return;
}
// ByteToNormalizedFloat preserves component order, so the packed ABGR bytes must first be shuffled to RGBA.
// Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation.
// The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed.
int lastIndex = source.Length - 1;
Span<Rgba32> temporary = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * source.Length) + 1, lastIndex);
PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast<Abgr32P, byte>(source[..lastIndex]), MemoryMarshal.Cast<Rgba32, byte>(temporary));
SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast<Rgba32, byte>(temporary), MemoryMarshal.Cast<Vector4, float>(destination[..lastIndex]));
destination[lastIndex] = source[lastIndex].ToVector4();
ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply));
}
/// <summary>
/// Converts premultiplied ABGR pixels to unassociated scaled vectors.
/// </summary>
/// <param name="source">The source pixels.</param>
/// <param name="destination">The destination vectors.</param>
internal static void ToUnassociatedVector4(ReadOnlySpan<Abgr32P> source, Span<Vector4> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
ref Abgr32P sourceBase = ref MemoryMarshal.GetReference(source);
ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination);
for (nuint i = 0; i < (uint)source.Length; i++)
{
Abgr32P pixel = Unsafe.Add(ref sourceBase, i);
Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A);
}
}
/// <summary>
/// Converts vectors to premultiplied ABGR pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
/// <param name="modifiers">The conversion modifier flags.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void FromVector4(
Span<Vector4> source,
Span<Abgr32P> destination,
PixelConversionModifiers modifiers)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply));
// NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer.
Span<byte> destinationBytes = MemoryMarshal.Cast<Abgr32P, byte>(destination);
SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes);
PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes);
}
/// <summary>
/// Converts associated scaled vectors to premultiplied ABGR pixels.
/// </summary>
/// <param name="source">The source vectors.</param>
/// <param name="destination">The destination pixels.</param>
internal static void FromAssociatedVector4(Span<Vector4> source, Span<Abgr32P> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
destination = destination[..source.Length];
ReassociateToByte(source);
Span<byte> destinationBytes = MemoryMarshal.Cast<Abgr32P, byte>(destination);
SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast<Vector4, float>(source), destinationBytes);
PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes);
}
/// <summary>
/// Converts a floating-point byte magnitude to a byte.
/// </summary>
/// <param name="value">The byte magnitude.</param>
/// <returns>The rounded and clamped byte.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ConvertToByte(float value) => (byte)Numerics.Clamp(value + .5F, 0, byte.MaxValue);
}
}

11
src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs

@ -15,10 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils;
internal static partial class Vector4Converters
{
/// <summary>
/// Provides efficient implementations for batched to/from <see cref="Vector4"/> conversion.
/// which is applicable for <see cref="Rgba32"/>-compatible pixel types where <see cref="IPixel.ToVector4"/>
/// returns the same scaled result as <see cref="IPixel.ToScaledVector4"/>.
/// The method is works by internally converting to a <see cref="Rgba32"/> therefore it's not applicable for that type!
/// Provides efficient implementations for batched conversion between RGBA-compatible pixel types and <see cref="Vector4"/> values.
/// </summary>
public static class RgbaCompatible
{
@ -60,14 +57,14 @@ internal static partial class Vector4Converters
return;
}
// Using the last quarter of 'destination' as a temporary buffer to avoid allocation:
// ToVector4 expands each pixel into a 16-byte vector. Reuse the unwritten destination tail as RGBA staging
// so pixelOperations can reorder the source without allocating a temporary row.
int countWithoutLastItem = count - 1;
ReadOnlySpan<TPixel> reducedSource = source[..countWithoutLastItem];
Span<Rgba32> lastQuarterOfDestination = MemoryMarshal.Cast<Vector4, Rgba32>(destination).Slice((3 * count) + 1, countWithoutLastItem);
pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestination);
// 'destination' and 'lastQuarterOfDestination' are overlapping buffers,
// but we are always reading/writing at different positions:
// Staging overlaps the final output vector, which remains unwritten until the staged bytes are consumed.
SimdUtils.ByteToNormalizedFloat(
MemoryMarshal.Cast<Rgba32, byte>(lastQuarterOfDestination),
MemoryMarshal.Cast<Vector4, float>(destination[..countWithoutLastItem]));

79
tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs

@ -14,7 +14,6 @@ using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.Short))]
public abstract class FromVector4<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -62,14 +61,21 @@ public abstract class FromVector4<TPixel>
=> PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, this.Source.GetSpan(), this.Destination.GetSpan());
}
[Config(typeof(Config.Short))]
public class FromVector4Rgba32 : FromVector4<Rgba32>
{
/// <summary>
/// Measures the raw SIMD kernel that converts normalized RGBA vector components to bytes.
/// </summary>
[Benchmark]
public void UseHwIntrinsics()
{
Span<float> sBytes = MemoryMarshal.Cast<Vector4, float>(this.Source.GetSpan());
Span<byte> dFloats = MemoryMarshal.Cast<Rgba32, byte>(this.Destination.GetSpan());
// Four components per pixel make every configured count divisible by the supported 128-, 256-, and 512-bit
// byte-vector widths. Calling the block kernel directly therefore excludes PixelOperations contract handling
// and the general conversion wrapper's remainder dispatch, establishing the lower bound for the SIMD conversion.
SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats);
}
@ -121,36 +127,45 @@ public class FromVector4Rgba32 : FromVector4<Rgba32>
}
/*
BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3085/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.200-preview.23624.5
[Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
Job-YJYLLR : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3
LaunchCount=1 WarmupCount=3
| Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|---------------------------- |------ |------------:|-------------:|-----------:|------:|--------:|----------:|------------:|
| PixelOperations_Base | 64 | 114.80 ns | 16.459 ns | 0.902 ns | 1.00 | 0.00 | - | NA |
| PixelOperations_Specialized | 64 | 28.91 ns | 80.482 ns | 4.411 ns | 0.25 | 0.04 | - | NA |
| FallbackIntrinsics128 | 64 | 133.60 ns | 23.750 ns | 1.302 ns | 1.16 | 0.02 | - | NA |
| ExtendedIntrinsic | 64 | 40.11 ns | 10.183 ns | 0.558 ns | 0.35 | 0.01 | - | NA |
| UseHwIntrinsics | 64 | 14.71 ns | 4.860 ns | 0.266 ns | 0.13 | 0.00 | - | NA |
| UseAvx2_Grouped | 64 | 20.23 ns | 11.619 ns | 0.637 ns | 0.18 | 0.00 | - | NA |
| | | | | | | | | |
| PixelOperations_Base | 256 | 387.94 ns | 31.591 ns | 1.732 ns | 1.00 | 0.00 | - | NA |
| PixelOperations_Specialized | 256 | 50.93 ns | 22.388 ns | 1.227 ns | 0.13 | 0.00 | - | NA |
| FallbackIntrinsics128 | 256 | 509.72 ns | 249.926 ns | 13.699 ns | 1.31 | 0.04 | - | NA |
| ExtendedIntrinsic | 256 | 140.32 ns | 9.353 ns | 0.513 ns | 0.36 | 0.00 | - | NA |
| UseHwIntrinsics | 256 | 41.99 ns | 16.000 ns | 0.877 ns | 0.11 | 0.00 | - | NA |
| UseAvx2_Grouped | 256 | 63.81 ns | 2.360 ns | 0.129 ns | 0.16 | 0.00 | - | NA |
| | | | | | | | | |
| PixelOperations_Base | 2048 | 2,979.49 ns | 2,023.706 ns | 110.926 ns | 1.00 | 0.00 | - | NA |
| PixelOperations_Specialized | 2048 | 326.19 ns | 19.077 ns | 1.046 ns | 0.11 | 0.00 | - | NA |
| FallbackIntrinsics128 | 2048 | 3,885.95 ns | 411.078 ns | 22.533 ns | 1.31 | 0.05 | - | NA |
| ExtendedIntrinsic | 2048 | 1,078.58 ns | 136.960 ns | 7.507 ns | 0.36 | 0.01 | - | NA |
| UseHwIntrinsics | 2048 | 312.07 ns | 68.662 ns | 3.764 ns | 0.10 | 0.00 | - | NA |
| UseAvx2_Grouped | 2048 | 451.83 ns | 41.742 ns | 2.288 ns | 0.15 | 0.01 | - | NA |
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET 8.0.28, X64 RyuJIT x86-64-v4
| Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|---------------------------- |------ |------------:|-----------:|----------:|------:|--------:|----------:|
| PixelOperations_Base | 64 | 60.19 ns | 32.376 ns | 1.775 ns | 1.00 | 0.04 | - |
| PixelOperations_Specialized | 64 | 22.94 ns | 66.082 ns | 3.622 ns | 0.38 | 0.05 | - |
| UseHwIntrinsics | 64 | 10.64 ns | 9.535 ns | 0.523 ns | 0.18 | 0.01 | - |
| UseAvx2_Grouped | 64 | 11.69 ns | 0.997 ns | 0.055 ns | 0.19 | 0.01 | - |
| PixelOperations_Base | 256 | 232.65 ns | 37.948 ns | 2.080 ns | 1.00 | 0.01 | - |
| PixelOperations_Specialized | 256 | 33.75 ns | 3.906 ns | 0.214 ns | 0.15 | 0.00 | - |
| UseHwIntrinsics | 256 | 21.91 ns | 2.732 ns | 0.150 ns | 0.09 | 0.00 | - |
| UseAvx2_Grouped | 256 | 24.84 ns | 5.333 ns | 0.292 ns | 0.11 | 0.00 | - |
| PixelOperations_Base | 2048 | 1,500.98 ns | 951.510 ns | 52.155 ns | 1.00 | 0.04 | - |
| PixelOperations_Specialized | 2048 | 156.21 ns | 65.074 ns | 3.567 ns | 0.10 | 0.00 | - |
| UseHwIntrinsics | 2048 | 144.38 ns | 40.947 ns | 2.244 ns | 0.10 | 0.00 | - |
| UseAvx2_Grouped | 2048 | 189.80 ns | 54.098 ns | 2.965 ns | 0.13 | 0.00 | - |
*/
}
/// <summary>
/// Measures bulk conversion from <see cref="Vector4"/> values to premultiplied <see cref="Bgra32P"/> pixels.
/// </summary>
[Config(typeof(Config.Analysis))]
public class FromVector4Bgra32P : FromVector4<Bgra32P>
{
/*
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET 8.0.28, X64 RyuJIT x86-64-v4
| Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Allocated |
|---------------------------- |------ |------------:|-----------:|----------:|------:|--------:|----------:|----------:|
| PixelOperations_Base | 64 | 59.73 ns | 19.644 ns | 1.077 ns | 1.00 | 0.02 | 1,152 B | - |
| PixelOperations_Specialized | 64 | 50.86 ns | 7.372 ns | 0.404 ns | 0.85 | 0.01 | 2,975 B | - |
| PixelOperations_Base | 256 | 214.03 ns | 70.798 ns | 3.881 ns | 1.00 | 0.02 | 1,152 B | - |
| PixelOperations_Specialized | 256 | 70.21 ns | 17.210 ns | 0.943 ns | 0.33 | 0.01 | 2,992 B | - |
| PixelOperations_Base | 2048 | 1,855.02 ns | 443.677 ns | 24.319 ns | 1.00 | 0.02 | 1,152 B | - |
| PixelOperations_Specialized | 2048 | 272.82 ns | 39.302 ns | 2.154 ns | 0.15 | 0.00 | 2,992 B | - |
*/
}

64
tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs

@ -20,24 +20,48 @@ public class ToVector4_Bgra32 : ToVector4<Bgra32>
this.Destination.GetSpan());
}
// RESULTS:
// Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
// ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:|
// PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B |
// PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B |
// | | | | | | | | | |
// PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B |
// PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B |
// | | | | | | | | | |
// PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B |
// PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B |
// | | | | | | | | | |
// PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B |
// PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B |
// | | | | | | | | | |
// PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B |
// PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B |
// | | | | | | | | | |
// PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B |
// PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B |
// BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2)
// AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
// .NET 8.0.28, X64 RyuJIT x86-64-v4
//
// | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
// |---------------------------- |------ |------------:|------------:|----------:|------:|--------:|----------:|
// | PixelOperations_Base | 64 | 58.30 ns | 20.13 ns | 1.103 ns | 1.00 | 0.02 | - |
// | PixelOperations_Specialized | 64 | 58.95 ns | 23.68 ns | 1.298 ns | 1.01 | 0.03 | - |
// | PixelOperations_Base | 256 | 226.95 ns | 84.03 ns | 4.606 ns | 1.00 | 0.02 | - |
// | PixelOperations_Specialized | 256 | 229.70 ns | 40.00 ns | 2.192 ns | 1.01 | 0.02 | - |
// | PixelOperations_Base | 2048 | 1,795.42 ns | 1,465.95 ns | 80.354 ns | 1.00 | 0.05 | - |
// | PixelOperations_Specialized | 2048 | 291.89 ns | 109.98 ns | 6.028 ns | 0.16 | 0.01 | - |
}
/// <summary>
/// Measures bulk conversion from premultiplied <see cref="Bgra32P"/> pixels to <see cref="System.Numerics.Vector4"/> values.
/// </summary>
[Config(typeof(Config.Analysis))]
public class ToVector4_Bgra32P : ToVector4<Bgra32P>
{
/// <summary>
/// Measures the scalar implementation used as the comparison baseline.
/// </summary>
[Benchmark(Baseline = true)]
public void PixelOperations_Base()
{
new AssociatedAlphaPixelOperations<Bgra32P>().ToVector4(
this.Configuration,
this.Source.GetSpan(),
this.Destination.GetSpan());
}
// BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2)
// AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
// .NET 8.0.28, X64 RyuJIT x86-64-v4
//
// | Method | Count | Mean | Error | StdDev | Ratio | Code Size | Allocated |
// |---------------------------- |------ |------------:|-----------:|---------:|------:|----------:|----------:|
// | PixelOperations_Base | 64 | 58.08 ns | 10.122 ns | 0.555 ns | 1.00 | 932 B | - |
// | PixelOperations_Specialized | 64 | 79.63 ns | 9.080 ns | 0.498 ns | 1.37 | 3,688 B | - |
// | PixelOperations_Base | 256 | 224.99 ns | 46.742 ns | 2.562 ns | 1.00 | 958 B | - |
// | PixelOperations_Specialized | 256 | 99.41 ns | 9.728 ns | 0.533 ns | 0.44 | 3,688 B | - |
// | PixelOperations_Base | 2048 | 1,745.77 ns | 143.244 ns | 7.852 ns | 1.00 | 958 B | - |
// | PixelOperations_Specialized | 2048 | 293.00 ns | 44.137 ns | 2.419 ns | 0.17 | 3,704 B | - |
}

29
tests/ImageSharp.Benchmarks/Config.cs

@ -8,6 +8,7 @@ using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Exporters.Json;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
@ -46,6 +47,34 @@ public partial class Config : ManualConfig
.WithArguments([new MsBuildArgument("/p:DebugType=portable")]));
}
/// <summary>
/// Configures a short diagnostic run that exports memory usage, generated code, and full measurement data.
/// Elevated Windows runs also include branch-instruction counters.
/// </summary>
public class Analysis : Config
{
public Analysis()
{
this.AddJob(Job.ShortRun.WithRuntime(CoreRuntime.Core80).WithArguments([new MsBuildArgument("/p:DebugType=portable")]));
this.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig(
maxDepth: 3,
printSource: true,
exportGithubMarkdown: true,
exportCombinedDisassemblyReport: true)));
this.AddExporter(JsonExporter.Full);
#if OS_WINDOWS
if (this.IsElevated)
{
// ETW hardware counters require elevation, so ordinary benchmark runs omit them instead of requesting more access.
this.AddHardwareCounters(HardwareCounter.BranchMispredictions, HardwareCounter.BranchInstructions);
}
#endif
}
}
public class StandardInProcess : Config
{
public StandardInProcess() => this.AddJob(

101
tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs

@ -0,0 +1,101 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders;
/// <summary>
/// Compares straight-alpha and associated-alpha bulk pixel blending.
/// </summary>
[Config(typeof(Config.Short))]
public class AssociatedAlphaPixelBlenderBenchmark
{
private const int PixelCount = 1024;
private readonly Rgba32[] unassociatedDestination = new Rgba32[PixelCount];
private readonly Rgba32[] unassociatedBackground = new Rgba32[PixelCount];
private readonly Rgba32[] unassociatedSource = new Rgba32[PixelCount];
private readonly Rgba32P[] associatedDestination = new Rgba32P[PixelCount];
private readonly Rgba32P[] associatedBackground = new Rgba32P[PixelCount];
private readonly Rgba32P[] associatedSource = new Rgba32P[PixelCount];
private readonly float[] amounts = new float[PixelCount];
private readonly Vector4[] unassociatedWorkingBuffer = new Vector4[PixelCount * 3];
private readonly Vector4[] associatedWorkingBuffer = new Vector4[PixelCount * 3];
private PixelBlender<Rgba32> unassociatedBlender;
private PixelBlender<Rgba32P> associatedBlender;
/// <summary>
/// Gets or sets the color-blending mode measured by the benchmark.
/// </summary>
[ParamsAllValues]
public PixelColorBlendingMode ColorMode { get; set; }
/// <summary>
/// Gets or sets the alpha-composition mode measured by the benchmark.
/// </summary>
[ParamsAllValues]
public PixelAlphaCompositionMode AlphaMode { get; set; }
/// <summary>
/// Initializes equivalent straight-alpha and associated-alpha rows.
/// </summary>
[GlobalSetup]
public void Setup()
{
this.unassociatedBlender = PixelOperations<Rgba32>.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode);
this.associatedBlender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode);
Random random = new(42);
for (int i = 0; i < PixelCount; i++)
{
Rgba32 background = new((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(64, 256));
Rgba32 source = new((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(64, 256));
this.unassociatedBackground[i] = background;
this.unassociatedSource[i] = source;
this.associatedBackground[i] = Rgba32P.FromRgba32(background);
this.associatedSource[i] = Rgba32P.FromRgba32(source);
this.amounts[i] = random.NextSingle();
}
}
/// <summary>
/// Blends one row stored with straight alpha.
/// </summary>
/// <returns>The last destination pixel.</returns>
[Benchmark(Description = "Straight alpha", Baseline = true)]
public Rgba32 BlendUnassociated()
{
this.unassociatedBlender.Blend<Rgba32>(
Configuration.Default,
this.unassociatedDestination,
this.unassociatedBackground,
this.unassociatedSource,
this.amounts,
this.unassociatedWorkingBuffer);
return this.unassociatedDestination[^1];
}
/// <summary>
/// Blends one row stored with associated alpha.
/// </summary>
/// <returns>The last destination pixel.</returns>
[Benchmark(Description = "Associated alpha")]
public Rgba32P BlendAssociated()
{
this.associatedBlender.Blend<Rgba32P>(
Configuration.Default,
this.associatedDestination,
this.associatedBackground,
this.associatedSource,
this.amounts,
this.associatedWorkingBuffer);
return this.associatedDestination[^1];
}
}

14
tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs

@ -35,6 +35,20 @@ public partial class ColorTests
Assert.Equal(source, data);
}
[Fact]
public void Rgba32P()
{
Rgba32P source = new(64, 32, 16, 128);
// Act:
Color color = Color.FromPixel(source);
// Assert:
Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation);
Assert.Equal(source.ToScaledVector4(), color.ToScaledVector4());
Assert.Equal(source, color.ToPixel<Rgba32P>());
}
[Fact]
public void Argb32()
{

59
tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs

@ -114,6 +114,65 @@ public partial class ColorTests
Assert.Equal(new L8(255), color.ToPixel<L8>());
}
[Fact]
public void AssociatedVectorConstructor()
{
Rgba32P expected = new(64, 32, 16, 128);
// Act:
Color color = Color.FromScaledVector(expected.ToScaledVector4(), PixelAlphaRepresentation.Associated);
// Assert:
Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation);
Assert.Equal(expected.ToScaledVector4(), color.ToScaledVector4());
Assert.Equal(expected, color.ToPixel<Rgba32P>());
Assert.Equal(new Rgba32(128, 64, 32, 128), color.ToPixel<Rgba32>());
}
[Fact]
public void UnassociatedPixelToAssociatedPixel()
{
Color color = Color.FromPixel(new Rgba32(128, 64, 32, 128));
// Act:
Rgba32P actual = color.ToPixel<Rgba32P>();
// Assert:
Assert.Equal(new Rgba32P(64, 32, 16, 128), actual);
}
[Fact]
public void AssociatedVectorSpanConstructor()
{
Rgba32P expected = new(64, 32, 16, 128);
Vector4[] source = [expected.ToScaledVector4()];
Color[] destination = new Color[source.Length];
// Act:
Color.FromScaledVector(source, destination, PixelAlphaRepresentation.Associated);
// Assert:
Assert.Equal(PixelAlphaRepresentation.Associated, destination[0].AlphaRepresentation);
Assert.Equal(expected.ToScaledVector4(), destination[0].ToScaledVector4());
Assert.Equal(expected, destination[0].ToPixel<Rgba32P>());
}
[Fact]
public void AssociatedPixelSpanRoundTrip()
{
Rgba32P[] source = [new(64, 32, 16, 128), new(24, 12, 6, 96)];
Color[] colors = new Color[source.Length];
Rgba32P[] destination = new Rgba32P[source.Length];
// Act:
Color.FromPixel<Rgba32P>(source, colors);
Color.ToPixel<Rgba32P>(colors, destination);
// Assert:
Assert.Equal(source, destination);
Assert.All(colors, color => Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation));
}
[Fact]
public void GenericPixelRoundTrip()
{

22
tests/ImageSharp.Tests/Color/ColorTests.cs

@ -18,6 +18,28 @@ public partial class ColorTests
Assert.Equal(expected, c2.ToPixel<Rgba32>());
}
[Fact]
public void WithAlphaPreservesAssociatedRepresentation()
{
Color color = Color.FromPixel(new Rgba32P(64, 32, 16, 128));
// Act:
Color actual = color.WithAlpha(64F / 255F);
// Assert:
Assert.Equal(PixelAlphaRepresentation.Associated, actual.AlphaRepresentation);
Assert.Equal(new Rgba32P(32, 16, 8, 64), actual.ToPixel<Rgba32P>());
Assert.Equal(new Rgba32(128, 64, 32, 64), actual.ToPixel<Rgba32>());
}
[Fact]
public void ToHexConvertsAssociatedRepresentation()
{
Color color = Color.FromPixel(new Rgba32P(64, 32, 16, 128));
Assert.Equal("80402080", color.ToHex());
}
[Theory]
[InlineData(false)]
[InlineData(true)]

43
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -148,11 +148,11 @@ public partial class SimdUtilsTests
{
byte[] source = new Random(count).GenerateRandomByteArray(count);
float[] result = new float[count];
float[] expected = source.Select(b => b / 255f).ToArray();
float[] expected = source.Select(b => b * (1F / byte.MaxValue)).ToArray();
convert(source, result);
Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f));
Assert.Equal(expected, result);
}
[Theory]
@ -193,6 +193,45 @@ public partial class SimdUtilsTests
}
}
[Fact]
public void BulkConvertNormalizedFloatToByteRoundsMidpointsAwayFromZero()
{
float[] midpointValues = Enumerable.Range(0, byte.MaxValue).Select(x => (x + 0.5F) / byte.MaxValue).ToArray();
float[] source = new float[1024];
byte[] expected = new byte[source.Length];
byte[] actual = new byte[source.Length];
for (int i = 0; i < source.Length; i++)
{
int value = i % midpointValues.Length;
source[i] = midpointValues[value];
expected[i] = (byte)(value + 1);
}
SimdUtils.NormalizedFloatToByteSaturate(source, actual);
Assert.Equal(expected, actual);
}
[Fact]
public void BulkConvertFloatToByteRoundsMidpointsAwayFromZeroAndClampsOverflows()
{
float[] source = new float[1027];
byte[] expected = new byte[source.Length];
byte[] actual = new byte[source.Length];
for (int i = 0; i < source.Length; i++)
{
float value = (i % 258) - .5F;
source[i] = value;
expected[i] = (byte)Math.Min(byte.MaxValue, Math.Max(0, value + .5F));
}
SimdUtils.FloatToByteSaturate(source, actual);
Assert.Equal(expected, actual);
}
[Theory]
[MemberData(nameof(ArbitraryArraySizes))]
public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes<Rgb24>(

667
tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs

@ -0,0 +1,667 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.Tests.PixelFormats;
/// <summary>
/// Verifies the shared contract for pixel formats that store associated alpha.
/// </summary>
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
[Trait("Category", "PixelFormats")]
public abstract class AssociatedAlphaPixelTests<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private static readonly ApproximateFloatComparer VectorComparer = new(.005F);
/// <summary>
/// Gets the color channels described by the pixel format.
/// </summary>
protected virtual PixelColorType ExpectedColorType => PixelColorType.RGB | PixelColorType.Alpha;
[Fact]
public void PixelInformationDescribesAssociatedAlpha()
{
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
PixelComponentInfo componentInfo = info.ComponentInfo.Value;
int expectedComponentPrecision = (Unsafe.SizeOf<TPixel>() * 8) / 4;
Assert.Equal(Unsafe.SizeOf<TPixel>() * 8, info.BitsPerPixel);
Assert.Equal(PixelAlphaRepresentation.Associated, info.AlphaRepresentation);
Assert.Equal(this.ExpectedColorType, info.ColorType);
Assert.Equal(4, componentInfo.ComponentCount);
Assert.Equal(0, componentInfo.Padding);
for (int i = 0; i < componentInfo.ComponentCount; i++)
{
Assert.Equal(expectedComponentPrecision, componentInfo.GetComponentPrecision(i));
}
}
[Fact]
public void ScaledVectorConversionsUseAssociatedComponents()
{
Vector4 associated = new(.25F, .125F, .0625F, .5F);
TPixel pixel = TPixel.FromScaledVector4(associated);
Assert.Equal(associated, pixel.ToScaledVector4(), VectorComparer);
}
[Fact]
public void FromRgba32AssociatesColorComponents()
{
Rgba32 source = new(192, 128, 64, 128);
Vector4 expected = source.ToScaledVector4();
Numerics.Premultiply(ref expected);
TPixel pixel = TPixel.FromRgba32(source);
Assert.Equal(expected, pixel.ToScaledVector4(), VectorComparer);
}
[Fact]
public void ToRgba32ReturnsUnassociatedColorComponents()
{
Rgba32 expected = new(192, 128, 64, 128);
TPixel pixel = TPixel.FromRgba32(expected);
Rgba32 actual = pixel.ToRgba32();
AssertRgba32Equal(expected, actual, 3);
}
[Fact]
public void ColorConversionsPreserveUnassociatedColor()
{
Rgba32 expected = new(192, 128, 64, 128);
TPixel source = TPixel.FromRgba32(expected);
Color color = Color.FromPixel(source);
Rgba32 actual = color.ToPixel<Rgba32>();
TPixel roundTrip = color.ToPixel<TPixel>();
AssertRgba32Equal(expected, actual, 3);
Assert.Equal(source.ToScaledVector4(), roundTrip.ToScaledVector4(), VectorComparer);
}
[Fact]
public void ScalarBlendingUsesUnassociatedColorValues()
{
TPixel background = TPixel.FromRgba32(new Rgba32(200, 40, 80, 160));
TPixel source = TPixel.FromRgba32(new Rgba32(20, 180, 100, 96));
PixelBlender<TPixel> associatedBlender = PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
PixelBlender<Rgba32> unassociatedBlender = new DefaultPixelBlenders<Rgba32>.NormalSrcOver();
Rgba32 expected = unassociatedBlender.Blend(background.ToRgba32(), source.ToRgba32(), .75F);
Rgba32 actual = associatedBlender.Blend(background, source, .75F).ToRgba32();
AssertRgba32Equal(expected, actual, 4);
}
private static void AssertRgba32Equal(Rgba32 expected, Rgba32 actual, int tolerance)
{
Assert.InRange(Math.Abs(expected.R - actual.R), 0, tolerance);
Assert.InRange(Math.Abs(expected.G - actual.G), 0, tolerance);
Assert.InRange(Math.Abs(expected.B - actual.B), 0, tolerance);
Assert.InRange(Math.Abs(expected.A - actual.A), 0, tolerance);
}
}
/// <summary>
/// Tests the <see cref="Rgba32P"/> pixel format.
/// </summary>
public class Rgba32PTests : AssociatedAlphaPixelTests<Rgba32P>
{
[Fact]
public void ByteLayoutAndPackedValue()
{
Rgba32P[] pixels = [new(1, 2, 3, 4)];
Assert.Equal(new byte[] { 1, 2, 3, 4 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray());
Assert.Equal(0x04030201U, pixels[0].PackedValue);
}
}
/// <summary>
/// Tests the <see cref="Bgra32P"/> pixel format.
/// </summary>
public class Bgra32PTests : AssociatedAlphaPixelTests<Bgra32P>
{
/// <inheritdoc />
protected override PixelColorType ExpectedColorType => PixelColorType.BGR | PixelColorType.Alpha;
[Fact]
public void ByteLayoutAndPackedValue()
{
Bgra32P[] pixels = [new(1, 2, 3, 4)];
Assert.Equal(new byte[] { 3, 2, 1, 4 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray());
Assert.Equal(0x04010203U, pixels[0].PackedValue);
}
}
/// <summary>
/// Tests the <see cref="Argb32P"/> pixel format.
/// </summary>
public class Argb32PTests : AssociatedAlphaPixelTests<Argb32P>
{
[Fact]
public void ByteLayoutAndPackedValue()
{
Argb32P[] pixels = [new(1, 2, 3, 4)];
Assert.Equal(new byte[] { 4, 1, 2, 3 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray());
Assert.Equal(0x03020104U, pixels[0].PackedValue);
}
}
/// <summary>
/// Tests the <see cref="Abgr32P"/> pixel format.
/// </summary>
public class Abgr32PTests : AssociatedAlphaPixelTests<Abgr32P>
{
/// <inheritdoc />
protected override PixelColorType ExpectedColorType => PixelColorType.BGR | PixelColorType.Alpha;
[Fact]
public void ByteLayoutAndPackedValue()
{
Abgr32P[] pixels = [new(1, 2, 3, 4)];
Assert.Equal(new byte[] { 4, 3, 2, 1 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray());
Assert.Equal(0x01020304U, pixels[0].PackedValue);
}
}
/// <summary>
/// Tests the <see cref="NormalizedByte4P"/> pixel format.
/// </summary>
public class NormalizedByte4PTests : AssociatedAlphaPixelTests<NormalizedByte4P>
{
[Fact]
public void PackedValueMatchesNormalizedByte4ForAssociatedVector()
{
Vector4 associated = new(.1F, -.3F, .5F, -.7F);
Assert.Equal(new NormalizedByte4(associated).PackedValue, new NormalizedByte4P(associated).PackedValue);
}
}
/// <summary>
/// Tests the <see cref="HalfVector4P"/> pixel format.
/// </summary>
public class HalfVector4PTests : AssociatedAlphaPixelTests<HalfVector4P>
{
[Fact]
public void PackedValueMatchesHalfVector4ForAssociatedVector()
{
Vector4 associated = new(.1F, -.3F, .5F, -.7F);
Assert.Equal(new HalfVector4(associated).PackedValue, new HalfVector4P(associated).PackedValue);
}
}
/// <summary>
/// Tests conversion between associated-alpha packed byte layouts.
/// </summary>
public class AssociatedAlphaPackedPixelConversionTests
{
[Fact]
public void Rgba32PToBgra32PRoundTripIsLossless() => AssertLosslessRoundTrip<Bgra32P>();
[Fact]
public void Rgba32PToArgb32PRoundTripIsLossless() => AssertLosslessRoundTrip<Argb32P>();
[Fact]
public void Rgba32PToAbgr32PRoundTripIsLossless() => AssertLosslessRoundTrip<Abgr32P>();
[Fact]
public void Rgba32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Rgba32P(r, g, b, a));
[Fact]
public void Bgra32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Bgra32P(r, g, b, a));
[Fact]
public void Argb32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Argb32P(r, g, b, a));
[Fact]
public void Abgr32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Abgr32P(r, g, b, a));
[Fact]
public void Rgba32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Rgba32P>();
[Fact]
public void Bgra32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Bgra32P>();
[Fact]
public void Argb32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Argb32P>();
[Fact]
public void Abgr32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual<Abgr32P>();
private static void AssertLosslessRoundTrip<TIntermediate>()
where TIntermediate : unmanaged, IPixel<TIntermediate>
{
Rgba32P[] expected =
[
new(0, 0, 0, 0),
new(1, 2, 3, 4),
new(31, 63, 95, 127),
new(64, 128, 192, 255),
new(255, 255, 255, 255),
];
TIntermediate[] intermediate = new TIntermediate[expected.Length];
Rgba32P[] actual = new Rgba32P[expected.Length];
PixelOperations<TIntermediate>.Instance.From<Rgba32P>(Configuration.Default, expected, intermediate);
PixelOperations<Rgba32P>.Instance.From<TIntermediate>(Configuration.Default, intermediate, actual);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i].ToScaledVector4(), intermediate[i].ToScaledVector4());
}
Assert.Equal(expected, actual);
}
private static void AssertScalarAndBulkAssociatedVectorsAreEqual<TPixel>(Func<byte, byte, byte, byte, TPixel> createPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel[] pixels = new TPixel[64];
Vector4[] actual = new Vector4[pixels.Length];
for (int i = 0; i < pixels.Length; i++)
{
int component = i * 4;
pixels[i] = createPixel((byte)component, (byte)(component + 1), (byte)(component + 2), (byte)(component + 3));
}
PixelOperations<TPixel>.Instance.ToAssociatedScaledVector4(Configuration.Default, pixels, actual);
for (int i = 0; i < pixels.Length; i++)
{
Assert.Equal(pixels[i].ToScaledVector4(), actual[i]);
}
}
private static void AssertScalarAndBulkFromAssociatedVectorsAreEqual<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
const int count = 259;
Vector4[] vectors = new Vector4[count];
TPixel[] expected = new TPixel[count];
TPixel[] actual = new TPixel[count];
AssociatedAlphaPixelOperations<TPixel> operations = (AssociatedAlphaPixelOperations<TPixel>)PixelOperations<TPixel>.Instance;
for (int i = 0; i < vectors.Length; i++)
{
// Alternating exact byte alpha values with fractional values covers both reassociation branches. The odd length also exercises the SIMD remainder.
float alpha = (i & 1) == 0 ? (i % 256) / 255F : ((i % 255) + .375F) / 255F;
vectors[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha;
vectors[i].W = alpha;
expected[i] = operations.FromAssociatedScaledVector4(vectors[i]);
}
operations.FromAssociatedScaledVector4(Configuration.Default, vectors, actual);
Assert.Equal(expected, actual);
}
}
/// <summary>
/// Tests conversion between associated and unassociated packed byte formats.
/// </summary>
public class AssociatedToUnassociatedPackedPixelConversionTests
{
[Fact]
public void Rgba32PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent()
{
for (int alpha = 0; alpha <= byte.MaxValue; alpha++)
{
// Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared.
for (int associated = 0; associated <= alpha; associated++)
{
// This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha.
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha);
Rgba32P source = new((byte)associated, 0, 0, (byte)alpha);
Rgba32 expectedUnassociated = new(unassociated, 0, 0, (byte)alpha);
Rgba32 actualUnassociated = source.ToRgba32();
Rgba32P actualRoundTrip = Rgba32P.FromRgba32(actualUnassociated);
Assert.Equal(expectedUnassociated, actualUnassociated);
Assert.Equal(source, actualRoundTrip);
}
}
}
[Fact]
public void ColorFromRgba32PPreservesEveryValidAssociatedComponent()
{
for (int alpha = 0; alpha <= byte.MaxValue; alpha++)
{
for (int associated = 0; associated <= alpha; associated++)
{
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha);
Rgba32P source = new((byte)associated, 0, 0, (byte)alpha);
Color color = Color.FromPixel(source);
Assert.Equal(new Rgba32(unassociated, 0, 0, (byte)alpha), color.ToPixel<Rgba32>());
Assert.Equal(source, color.ToPixel<Rgba32P>());
}
}
}
[Fact]
public void Rgba32PToBgra32RoundTripPreservesEveryValidAssociatedComponent()
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha));
[Fact]
public void Bgra32PToBgra32RoundTripPreservesEveryValidAssociatedComponent()
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha));
[Fact]
public void Argb32PToBgra32RoundTripPreservesEveryValidAssociatedComponent()
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha));
[Fact]
public void Abgr32PToBgra32RoundTripPreservesEveryValidAssociatedComponent()
=> AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha));
[Fact]
public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent()
{
for (int alpha = 0; alpha < byte.MaxValue; alpha++)
{
for (int associated = 0; associated <= alpha; associated++)
{
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha);
byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254);
NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha);
Rgba32 actualUnassociated = source.ToRgba32();
NormalizedByte4P actualRoundTrip = NormalizedByte4P.FromRgba32(actualUnassociated);
Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), actualUnassociated);
Assert.Equal(source, actualRoundTrip);
}
}
}
[Fact]
public void ColorFromNormalizedByte4PPreservesEveryValidAssociatedComponent()
{
for (int alpha = 0; alpha < byte.MaxValue; alpha++)
{
for (int associated = 0; associated <= alpha; associated++)
{
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha);
byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254);
NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha);
Color color = Color.FromPixel(source);
Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), color.ToPixel<Rgba32>());
Assert.Equal(source, color.ToPixel<NormalizedByte4P>());
}
}
}
[Fact]
public void NormalizedByte4PToBgra32RoundTripPreservesEveryValidAssociatedComponent()
{
const int pairCount = 32640;
const int channelCount = 3;
NormalizedByte4P[] source = new NormalizedByte4P[pairCount * channelCount];
Bgra32[] expectedUnassociated = new Bgra32[source.Length];
Bgra32[] actualUnassociated = new Bgra32[source.Length];
NormalizedByte4P[] actualRoundTrip = new NormalizedByte4P[source.Length];
int index = 0;
for (int alpha = 0; alpha < byte.MaxValue; alpha++)
{
for (int associated = 0; associated <= alpha; associated++)
{
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha);
byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254);
source[index] = CreateNormalizedByte4P(associated, 0, 0, alpha);
expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, unassociatedAlpha);
source[index] = CreateNormalizedByte4P(0, associated, 0, alpha);
expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, unassociatedAlpha);
source[index] = CreateNormalizedByte4P(0, 0, associated, alpha);
expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, unassociatedAlpha);
}
}
PixelOperations<Bgra32>.Instance.From<NormalizedByte4P>(Configuration.Default, source, actualUnassociated);
PixelOperations<NormalizedByte4P>.Instance.From<Bgra32>(Configuration.Default, actualUnassociated, actualRoundTrip);
Assert.Equal(expectedUnassociated, actualUnassociated);
Assert.Equal(source, actualRoundTrip);
}
private static void AssertUnsignedByteBulkRoundTrip<TPixel>(Func<byte, byte, byte, byte, TPixel> createPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
const int pairCount = 32896;
const int channelCount = 3;
TPixel[] source = new TPixel[pairCount * channelCount];
Bgra32[] expectedUnassociated = new Bgra32[source.Length];
Bgra32[] actualUnassociated = new Bgra32[source.Length];
TPixel[] actualRoundTrip = new TPixel[source.Length];
int index = 0;
for (int alpha = 0; alpha <= byte.MaxValue; alpha++)
{
// Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared.
for (int associated = 0; associated <= alpha; associated++)
{
// This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha.
byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha);
source[index] = createPixel((byte)associated, 0, 0, (byte)alpha);
expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, (byte)alpha);
source[index] = createPixel(0, (byte)associated, 0, (byte)alpha);
expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, (byte)alpha);
source[index] = createPixel(0, 0, (byte)associated, (byte)alpha);
expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, (byte)alpha);
}
}
PixelOperations<Bgra32>.Instance.From<TPixel>(Configuration.Default, source, actualUnassociated);
PixelOperations<TPixel>.Instance.From<Bgra32>(Configuration.Default, actualUnassociated, actualRoundTrip);
Assert.Equal(expectedUnassociated, actualUnassociated);
Assert.Equal(source, actualRoundTrip);
}
private static NormalizedByte4P CreateNormalizedByte4P(int red, int green, int blue, int alpha)
{
uint packed = (byte)(red - 127)
| ((uint)(byte)(green - 127) << 8)
| ((uint)(byte)(blue - 127) << 16)
| ((uint)(byte)(alpha - 127) << 24);
return new NormalizedByte4P { PackedValue = packed };
}
}
/// <summary>
/// Tests that associated destinations use their stored alpha value when associating color components.
/// </summary>
public class AssociatedDestinationAlphaQuantizationTests
{
[Fact]
public void Rgba32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Rgba32P>();
[Fact]
public void Bgra32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Bgra32P>();
[Fact]
public void Argb32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Argb32P>();
[Fact]
public void Abgr32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<Abgr32P>();
[Fact]
public void NormalizedByte4PQuantizesDestinationAlphaBeforeAssociation()
{
ReadOnlySpan<byte> components = [64, 127, 191];
Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length];
NormalizedByte4P[] actualBulk = new NormalizedByte4P[source.Length];
int index = 0;
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
foreach (byte component in components)
{
source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha);
}
}
PixelOperations<NormalizedByte4P>.Instance.From<Rgba64>(Configuration.Default, source, actualBulk);
index = 0;
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
int expectedAlpha = ((alpha * 254) + 32767) / ushort.MaxValue;
foreach (byte component in components)
{
int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue;
NormalizedByte4P actualScalar = NormalizedByte4P.FromRgba64(source[index]);
int scalarRed = (sbyte)actualScalar.PackedValue + 127;
int scalarAlpha = (sbyte)(actualScalar.PackedValue >> 24) + 127;
int bulkRed = (sbyte)actualBulk[index].PackedValue + 127;
int bulkAlpha = (sbyte)(actualBulk[index].PackedValue >> 24) + 127;
Assert.Equal(expectedRed, scalarRed);
Assert.Equal(expectedAlpha, scalarAlpha);
Assert.Equal(expectedRed, bulkRed);
Assert.Equal(expectedAlpha, bulkAlpha);
index++;
}
}
}
[Fact]
public void HalfVector4PQuantizesDestinationAlphaBeforeAssociation()
{
ReadOnlySpan<byte> components = [64, 127, 191];
Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length];
HalfVector4P[] actualBulk = new HalfVector4P[source.Length];
int index = 0;
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
foreach (byte component in components)
{
source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha);
}
}
PixelOperations<HalfVector4P>.Instance.From<Rgba64>(Configuration.Default, source, actualBulk);
index = 0;
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
float nativeAlpha = ((alpha / (float)ushort.MaxValue) * 2F) - 1F;
ushort expectedAlpha = BitConverter.HalfToUInt16Bits((Half)nativeAlpha);
float storedAlpha = ((float)BitConverter.UInt16BitsToHalf(expectedAlpha) + 1F) / 2F;
foreach (byte component in components)
{
float associatedRed = ((component / (float)byte.MaxValue) * storedAlpha * 2F) - 1F;
ushort expectedRed = BitConverter.HalfToUInt16Bits((Half)associatedRed);
HalfVector4P actualScalar = HalfVector4P.FromRgba64(source[index]);
Assert.Equal(expectedRed, (ushort)actualScalar.PackedValue);
Assert.Equal(expectedAlpha, (ushort)(actualScalar.PackedValue >> 48));
Assert.Equal(expectedRed, (ushort)actualBulk[index].PackedValue);
Assert.Equal(expectedAlpha, (ushort)(actualBulk[index].PackedValue >> 48));
index++;
}
}
}
[Fact]
public void ColorToRgba32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Rgba32P>();
[Fact]
public void ColorToBgra32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Bgra32P>();
[Fact]
public void ColorToArgb32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Argb32P>();
[Fact]
public void ColorToAbgr32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<Abgr32P>();
[Fact]
public void ColorToNormalizedByte4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<NormalizedByte4P>();
[Fact]
public void ColorToHalfVector4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation<HalfVector4P>();
/// <summary>
/// Verifies the unsigned-byte destination grid through scalar and bulk conversion entry points.
/// </summary>
/// <typeparam name="TPixel">The associated unsigned-byte pixel format.</typeparam>
private static void AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
ReadOnlySpan<byte> components = [64, 127, 191];
Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length];
TPixel[] actualBulk = new TPixel[source.Length];
int index = 0;
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
foreach (byte component in components)
{
source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha);
}
}
PixelOperations<TPixel>.Instance.From<Rgba64>(Configuration.Default, source, actualBulk);
index = 0;
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
int expectedAlpha = ((alpha * byte.MaxValue) + 32767) / ushort.MaxValue;
foreach (byte component in components)
{
int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue;
Vector4 expected = new Vector4(expectedRed, 0, 0, expectedAlpha) * (1F / byte.MaxValue);
Assert.Equal(expected, TPixel.FromRgba64(source[index]).ToScaledVector4());
Assert.Equal(expected, actualBulk[index].ToScaledVector4());
index++;
}
}
}
/// <summary>
/// Verifies that <see cref="Color"/> delegates association to the destination pixel operations.
/// </summary>
/// <typeparam name="TPixel">The associated destination pixel format.</typeparam>
private static void AssertColorUsesDestinationAlphaRepresentation<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
for (int alpha = 0; alpha <= ushort.MaxValue; alpha++)
{
ushort component = (ushort)((byte)alpha * 257);
Rgba64 source = new(component, 0, 0, (ushort)alpha);
TPixel expected = TPixel.FromRgba64(source);
TPixel actual = Color.FromPixel(source).ToPixel<TPixel>();
Assert.Equal(expected, actual);
}
}
}

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

@ -222,6 +222,22 @@ public class PixelBlenderTests
ExerciseAllBlenderModeCombinations,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void AssociatedAlphaBlendFunctionsAreCalledForAllModeCombinations() =>
FeatureTestRunner.RunWithHwIntrinsicsFeature(
ExerciseAllAssociatedAlphaBlenderModeCombinations,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void AssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero()
{
Rgba32P background = Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160));
Rgba32P source = Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96));
PixelBlender<Rgba32P> blender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(PixelColorBlendingMode.HardLight, PixelAlphaCompositionMode.DestAtop);
Assert.Equal(new Rgba32P(30, 33, 16, 60), blender.Blend(background, source, .625F));
}
[Fact]
public void Blend_WithConstantSourceAndSingleAmount()
{
@ -624,22 +640,167 @@ public class PixelBlenderTests
}
}
private static void ExerciseAllAssociatedAlphaBlenderModeCombinations()
{
Rgba32P[] background =
[
Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)),
Rgba32P.FromRgba32(new Rgba32(40, 200, 100, 192)),
Rgba32P.FromRgba32(new Rgba32(120, 60, 230, 224)),
Rgba32P.FromRgba32(new Rgba32(180, 160, 20, 128)),
Rgba32P.FromRgba32(new Rgba32(30, 140, 210, 96)),
];
Rgba32P[] source =
[
Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)),
Rgba32P.FromRgba32(new Rgba32(210, 30, 150, 144)),
Rgba32P.FromRgba32(new Rgba32(80, 220, 40, 176)),
Rgba32P.FromRgba32(new Rgba32(240, 110, 60, 208)),
Rgba32P.FromRgba32(new Rgba32(100, 50, 200, 112)),
];
foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues<PixelAlphaCompositionMode>())
{
foreach (PixelColorBlendingMode colorMode in Enum.GetValues<PixelColorBlendingMode>())
{
PixelBlender<Rgba32P> blender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(colorMode, alphaMode);
AssertAssociatedBlenderMatchesScalar(blender, background, source, colorMode, alphaMode);
}
}
}
private static void AssertAssociatedBlenderMatchesScalar(
PixelBlender<Rgba32P> blender,
Rgba32P[] associatedBackground,
Rgba32P[] associatedSource,
PixelColorBlendingMode colorMode,
PixelAlphaCompositionMode alphaMode)
{
const float amount = .625F;
float[] amounts = [.125F, .375F, .625F, .875F, 1F];
float[] coverage = [.2F, .4F, .6F, .8F, 1F];
Rgba32P constantAssociatedSource = associatedSource[2];
Rgba32P[] expected = new Rgba32P[associatedBackground.Length];
Rgba32P[] actual = new Rgba32P[associatedBackground.Length];
Vector4[] actualSourceSpanBuffer = new Vector4[associatedBackground.Length * 3];
Vector4[] actualConstantSourceBuffer = new Vector4[associatedBackground.Length * 2];
for (int i = 0; i < expected.Length; i++)
{
expected[i] = blender.Blend(associatedBackground[i], associatedSource[i], amount);
}
blender.Blend<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amount, actualSourceSpanBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanSingleAmount");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = blender.Blend(associatedBackground[i], constantAssociatedSource, amount);
}
blender.Blend(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amount, actualConstantSourceBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceSingleAmount");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = blender.Blend(associatedBackground[i], associatedSource[i], amounts[i]);
}
blender.Blend<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amounts, actualSourceSpanBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanAmountSpan");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = blender.Blend(associatedBackground[i], constantAssociatedSource, amounts[i]);
}
blender.Blend(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amounts, actualConstantSourceBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceAmountSpan");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], associatedSource[i], amount, coverage[i]);
}
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amount, coverage, actualSourceSpanBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanSingleAmountCoverage");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], constantAssociatedSource, amount, coverage[i]);
}
blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amount, coverage, actualConstantSourceBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceSingleAmountCoverage");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], associatedSource[i], amounts[i], coverage[i]);
}
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amounts, coverage, actualSourceSpanBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanAmountSpanCoverage");
for (int i = 0; i < expected.Length; i++)
{
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], constantAssociatedSource, amounts[i], coverage[i]);
}
blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amounts, coverage, actualConstantSourceBuffer);
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceAmountSpanCoverage");
}
private static Rgba32P BlendWithCoverageScalar(PixelBlender<Rgba32P> blender, Rgba32P background, Rgba32P source, float amount, float coverage)
{
Span<Rgba32P> destination = stackalloc Rgba32P[1];
Span<Rgba32P> backgroundSpan = stackalloc Rgba32P[1] { background };
Span<Rgba32P> sourceSpan = stackalloc Rgba32P[1] { source };
Span<float> coverageSpan = stackalloc float[1] { coverage };
Span<Vector4> buffer = stackalloc Vector4[3];
// A one-pixel span takes the scalar remainder path, providing an exact oracle for each SIMD coverage result.
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer);
return destination[0];
}
private static void AssertRgba32PEqual(
ReadOnlySpan<Rgba32P> expected,
ReadOnlySpan<Rgba32P> actual,
PixelColorBlendingMode colorMode,
PixelAlphaCompositionMode alphaMode,
string scenario)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.True(expected[i] == actual[i], $"{colorMode}/{alphaMode}/{scenario}[{i}]: expected {expected[i]}, actual {actual[i]}");
}
}
private static void ExerciseBlender(PixelBlender<Rgba32> blender)
{
Rgba32 background = Color.MistyRose.ToPixel<Rgba32>();
Rgba32 source = Color.MidnightBlue.ToPixel<Rgba32>();
ExerciseBlender(blender, background, source);
}
private static void ExerciseBlender<TPixel>(PixelBlender<TPixel> blender, TPixel background, TPixel source)
where TPixel : unmanaged, IPixel<TPixel>
{
float[] amount = [1F, 1F, 1F, 1F];
float[] coverage = [1F, 1F, 1F, 1F];
Rgba32 expected = blender.Blend(background, source, 1F);
TPixel expected = blender.Blend(background, source, 1F);
Rgba32[] destination = new Rgba32[4];
Rgba32[] backgroundSpan = [background, background, background, background];
Rgba32[] sourceSpan = [source, source, source, source];
TPixel[] destination = new TPixel[4];
TPixel[] backgroundSpan = [background, background, background, background];
TPixel[] sourceSpan = [source, source, source, source];
Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3];
Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2];
blender.Blend<Rgba32>(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, sourceSpanBuffer);
blender.Blend<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, sourceSpanBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
blender.Blend(Configuration.Default, destination, backgroundSpan, source, 1F, constantSourceBuffer);
@ -651,7 +812,7 @@ 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);
blender.BlendWithCoverage<TPixel>(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);

8
tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
@ -73,13 +74,11 @@ public abstract partial class PixelConverterTests
}
internal static void To<TSourcePixel, TDestinationPixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<TDestinationPixel> destinationPixels)
where TSourcePixel : unmanaged, IPixel<TSourcePixel>
where TDestinationPixel : unmanaged, IPixel<TDestinationPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
int count = sourcePixels.Length;
@ -92,13 +91,14 @@ public abstract partial class PixelConverterTests
return;
}
// Normal conversion
// Scalar source and destination boundaries are the reference for the bulk operation: each format owns any representation conversion and destination quantization it requires.
ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels);
for (int i = 0; i < count; i++)
{
ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i);
ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i);
dp = TDestinationPixel.FromScaledVector4(sp.ToScaledVector4());
Vector4 vector = PixelOperations<TSourcePixel>.Instance.ToUnassociatedScaledVector4(sp);
dp = PixelOperations<TDestinationPixel>.Instance.FromUnassociatedScaledVector4(vector);
}
}
}

93
tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// <auto-generated />
@ -7,7 +7,6 @@ using SixLabors.ImageSharp.PixelFormats;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations;
public partial class PixelOperationsTests
@ -43,6 +42,21 @@ public partial class PixelOperationsTests
}
}
public partial class Argb32P_OperationsTests : PixelOperationsTests<Argb32P>
{
public Argb32P_OperationsTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void PixelTypeInfoHasCorrectAlphaRepresentation()
{
var alphaRepresentation = Argb32P.GetPixelTypeInfo().AlphaRepresentation;
Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation);
}
}
public partial class Abgr32_OperationsTests : PixelOperationsTests<Abgr32>
{
public Abgr32_OperationsTests(ITestOutputHelper output)
@ -58,6 +72,21 @@ public partial class PixelOperationsTests
}
}
public partial class Abgr32P_OperationsTests : PixelOperationsTests<Abgr32P>
{
public Abgr32P_OperationsTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void PixelTypeInfoHasCorrectAlphaRepresentation()
{
var alphaRepresentation = Abgr32P.GetPixelTypeInfo().AlphaRepresentation;
Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation);
}
}
public partial class Bgr24_OperationsTests : PixelOperationsTests<Bgr24>
{
public Bgr24_OperationsTests(ITestOutputHelper output)
@ -103,6 +132,21 @@ public partial class PixelOperationsTests
}
}
public partial class Bgra32P_OperationsTests : PixelOperationsTests<Bgra32P>
{
public Bgra32P_OperationsTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void PixelTypeInfoHasCorrectAlphaRepresentation()
{
var alphaRepresentation = Bgra32P.GetPixelTypeInfo().AlphaRepresentation;
Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation);
}
}
public partial class Bgra4444_OperationsTests : PixelOperationsTests<Bgra4444>
{
public Bgra4444_OperationsTests(ITestOutputHelper output)
@ -193,6 +237,21 @@ public partial class PixelOperationsTests
}
}
public partial class HalfVector4P_OperationsTests : PixelOperationsTests<HalfVector4P>
{
public HalfVector4P_OperationsTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void PixelTypeInfoHasCorrectAlphaRepresentation()
{
var alphaRepresentation = HalfVector4P.GetPixelTypeInfo().AlphaRepresentation;
Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation);
}
}
public partial class L16_OperationsTests : PixelOperationsTests<L16>
{
public L16_OperationsTests(ITestOutputHelper output)
@ -283,6 +342,21 @@ public partial class PixelOperationsTests
}
}
public partial class NormalizedByte4P_OperationsTests : PixelOperationsTests<NormalizedByte4P>
{
public NormalizedByte4P_OperationsTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void PixelTypeInfoHasCorrectAlphaRepresentation()
{
var alphaRepresentation = NormalizedByte4P.GetPixelTypeInfo().AlphaRepresentation;
Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation);
}
}
public partial class NormalizedShort2_OperationsTests : PixelOperationsTests<NormalizedShort2>
{
public NormalizedShort2_OperationsTests(ITestOutputHelper output)
@ -388,6 +462,21 @@ public partial class PixelOperationsTests
}
}
public partial class Rgba32P_OperationsTests : PixelOperationsTests<Rgba32P>
{
public Rgba32P_OperationsTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void PixelTypeInfoHasCorrectAlphaRepresentation()
{
var alphaRepresentation = Rgba32P.GetPixelTypeInfo().AlphaRepresentation;
Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation);
}
}
public partial class Rgba64_OperationsTests : PixelOperationsTests<Rgba64>
{
public Rgba64_OperationsTests(ITestOutputHelper output)

16
tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude

@ -33,28 +33,41 @@ using Xunit.Abstractions;
"Short4"
];
private static readonly string[] AssociatedAlphaPixelTypes = [];
private static readonly string[] AssociatedAlphaPixelTypes =
[
"Argb32P",
"Abgr32P",
"Bgra32P",
"HalfVector4P",
"NormalizedByte4P",
"Rgba32P"
];
private static readonly string[] CommonPixelTypes =
[
"A8",
"Argb32",
"Argb32P",
"Abgr32",
"Abgr32P",
"Bgr24",
"Bgr565",
"Bgra32",
"Bgra32P",
"Bgra4444",
"Bgra5551",
"Byte4",
"HalfSingle",
"HalfVector2",
"HalfVector4",
"HalfVector4P",
"L16",
"L8",
"La16",
"La32",
"NormalizedByte2",
"NormalizedByte4",
"NormalizedByte4P",
"NormalizedShort2",
"NormalizedShort4",
"Rg32",
@ -62,6 +75,7 @@ using Xunit.Abstractions;
"Rgb48",
"Rgba1010102",
"Rgba32",
"Rgba32P",
"Rgba64",
"RgbaVector",
"Short2",

135
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -70,7 +70,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
protected virtual PixelOperations<TPixel> Operations { get; } = PixelOperations<TPixel>.Instance;
protected bool HasUnassociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated;
protected bool HasAssociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated;
internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction<Vector4> vectorModifier = null)
{
@ -185,7 +185,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
{
void SourceAction(ref Vector4 v)
{
if (this.HasUnassociatedAlpha)
if (!this.HasAssociatedAlpha)
{
Numerics.Premultiply(ref v);
}
@ -193,7 +193,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
void ExpectedAction(ref Vector4 v)
{
if (this.HasUnassociatedAlpha)
if (!this.HasAssociatedAlpha)
{
Numerics.UnPremultiply(ref v);
}
@ -207,11 +207,11 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers);
this.Operations.FromVector4Destructive(
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.Premultiply);
});
}
@ -221,7 +221,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
{
void SourceAction(ref Vector4 v)
{
if (this.HasUnassociatedAlpha)
if (!this.HasAssociatedAlpha)
{
Numerics.Premultiply(ref v);
}
@ -229,7 +229,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
void ExpectedAction(ref Vector4 v)
{
if (this.HasUnassociatedAlpha)
if (!this.HasAssociatedAlpha)
{
Numerics.UnPremultiply(ref v);
}
@ -243,15 +243,11 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
this.Configuration,
s,
d.GetSpan(),
modifiers | PixelConversionModifiers.Scale);
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
});
}
@ -263,7 +259,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
{
v = SRgbCompanding.Expand(v);
if (this.HasUnassociatedAlpha)
if (!this.HasAssociatedAlpha)
{
Numerics.Premultiply(ref v);
}
@ -271,7 +267,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
void ExpectedAction(ref Vector4 v)
{
if (this.HasUnassociatedAlpha)
if (!this.HasAssociatedAlpha)
{
Numerics.UnPremultiply(ref v);
}
@ -287,15 +283,11 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
this.Configuration,
s,
d.GetSpan(),
modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale);
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale);
},
false);
}
@ -317,22 +309,27 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
{
new TestPixel<A8>(),
new TestPixel<Abgr32>(),
new TestPixel<Abgr32P>(),
new TestPixel<Argb32>(),
new TestPixel<Argb32P>(),
new TestPixel<Bgr24>(),
new TestPixel<Bgr565>(),
new TestPixel<Bgra32>(),
new TestPixel<Bgra32P>(),
new TestPixel<Bgra4444>(),
new TestPixel<Bgra5551>(),
new TestPixel<Byte4>(),
new TestPixel<HalfSingle>(),
new TestPixel<HalfVector2>(),
new TestPixel<HalfVector4>(),
new TestPixel<HalfVector4P>(),
new TestPixel<L16>(),
new TestPixel<L8>(),
new TestPixel<La16>(),
new TestPixel<La32>(),
new TestPixel<NormalizedByte2>(),
new TestPixel<NormalizedByte4>(),
new TestPixel<NormalizedByte4P>(),
new TestPixel<NormalizedShort2>(),
new TestPixel<NormalizedShort4>(),
new TestPixel<Rg32>(),
@ -340,6 +337,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
new TestPixel<Rgb48>(),
new TestPixel<Rgba1010102>(),
new TestPixel<Rgba32>(),
new TestPixel<Rgba32P>(),
new TestPixel<Rgba64>(),
new TestPixel<RgbaVector>(),
new TestPixel<Short2>(),
@ -355,7 +353,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
TPixel[] source = CreatePixelTestData(count);
TDestPixel[] expected = new TDestPixel[count];
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected);
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(source, expected);
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false);
}
@ -408,7 +406,13 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
{
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
void ExpectedAction(ref Vector4 v)
{
if (!this.HasAssociatedAlpha)
{
Numerics.Premultiply(ref v);
}
}
TPixel[] source = CreatePixelTestData(count, SourceAction);
Vector4[] expected = CreateExpectedVector4Data(source, ExpectedAction);
@ -427,7 +431,13 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
{
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
void ExpectedAction(ref Vector4 v)
{
if (!this.HasAssociatedAlpha)
{
Numerics.Premultiply(ref v);
}
}
TPixel[] source = CreateScaledPixelTestData(count, SourceAction);
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -453,7 +463,11 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
void ExpectedAction(ref Vector4 v)
{
v = SRgbCompanding.Expand(v);
Numerics.Premultiply(ref v);
if (!this.HasAssociatedAlpha)
{
Numerics.Premultiply(ref v);
}
}
TPixel[] source = CreateScaledPixelTestData(count, SourceAction);
@ -499,7 +513,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
Argb32 argb = Argb32.FromScaledVector4(source[i].ToScaledVector4());
Argb32 argb = Argb32.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
expected[i4] = argb.A;
expected[i4 + 1] = argb.R;
@ -543,7 +557,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i3 = i * 3;
Bgr24 bgr = Bgr24.FromScaledVector4(source[i].ToScaledVector4());
Bgr24 bgr = Bgr24.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
expected[i3] = bgr.B;
expected[i3 + 1] = bgr.G;
expected[i3 + 2] = bgr.R;
@ -585,7 +599,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
Bgra32 bgra = Bgra32.FromScaledVector4(source[i].ToScaledVector4());
Bgra32 bgra = Bgra32.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
expected[i4] = bgra.B;
expected[i4 + 1] = bgra.G;
expected[i4 + 2] = bgra.R;
@ -628,7 +642,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
Abgr32 abgr = Abgr32.FromScaledVector4(source[i].ToScaledVector4());
Abgr32 abgr = Abgr32.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
expected[i4] = abgr.A;
expected[i4 + 1] = abgr.B;
expected[i4 + 2] = abgr.G;
@ -674,7 +688,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int offset = i * size;
Bgra5551 bgra = Bgra5551.FromScaledVector4(source[i].ToScaledVector4());
Bgra5551 bgra = Bgra5551.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
OctetBytes bytes = Unsafe.As<Bgra5551, OctetBytes>(ref bgra);
expected[offset] = bytes[0];
expected[offset + 1] = bytes[1];
@ -714,7 +728,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
expected[i] = L8.FromScaledVector4(source[i].ToScaledVector4());
expected[i] = L8.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
}
TestOperation(
@ -751,7 +765,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
expected[i] = L16.FromScaledVector4(source[i].ToScaledVector4());
expected[i] = L16.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
}
TestOperation(
@ -793,7 +807,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int offset = i * size;
La16 la = La16.FromScaledVector4(source[i].ToScaledVector4());
La16 la = La16.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
OctetBytes bytes = Unsafe.As<La16, OctetBytes>(ref la);
expected[offset] = bytes[0];
expected[offset + 1] = bytes[1];
@ -838,7 +852,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int offset = i * size;
La32 la = La32.FromScaledVector4(source[i].ToScaledVector4());
La32 la = La32.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
OctetBytes bytes = Unsafe.As<La32, OctetBytes>(ref la);
expected[offset] = bytes[0];
expected[offset + 1] = bytes[1];
@ -882,7 +896,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i3 = i * 3;
Rgb24 rgb = Rgb24.FromScaledVector4(source[i].ToScaledVector4());
Rgb24 rgb = Rgb24.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
expected[i3] = rgb.R;
expected[i3 + 1] = rgb.G;
expected[i3 + 2] = rgb.B;
@ -924,7 +938,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
Rgba32 rgba = Rgba32.FromScaledVector4(source[i].ToScaledVector4());
Rgba32 rgba = Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
expected[i4] = rgba.R;
expected[i4 + 1] = rgba.G;
expected[i4 + 2] = rgba.B;
@ -967,7 +981,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i6 = i * 6;
Rgb48 rgb = Rgb48.FromScaledVector4(source[i].ToScaledVector4());
Rgb48 rgb = Rgb48.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
OctetBytes rgb48Bytes = Unsafe.As<Rgb48, OctetBytes>(ref rgb);
expected[i6] = rgb48Bytes[0];
expected[i6 + 1] = rgb48Bytes[1];
@ -1013,7 +1027,7 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
for (int i = 0; i < count; i++)
{
int i8 = i * 8;
Rgba64 rgba = Rgba64.FromScaledVector4(source[i].ToScaledVector4());
Rgba64 rgba = Rgba64.FromScaledVector4(ToUnassociatedScaledVector4(source[i]));
OctetBytes rgba64Bytes = Unsafe.As<Rgba64, OctetBytes>(ref rgba);
expected[i8] = rgba64Bytes[0];
expected[i8 + 1] = rgba64Bytes[1];
@ -1072,6 +1086,10 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
return expected;
}
private static Vector4 ToUnassociatedScaledVector4(TPixel source)
// The scalar conversion boundary owns representation-specific rounding. Byte-export tests compare each bulk operation with that scalar result.
=> PixelOperations<TPixel>.Instance.ToUnassociatedScaledVector4(source);
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
@ -1208,15 +1226,34 @@ public abstract class PixelOperationsTests<TPixel> : MeasureFixture
Assert.Equal(expected[i], actual[i], comparer);
}
}
else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel())
else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)))
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan();
ApproximateFloatComparer comparer = new(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F);
for (int i = 0; i < count; i++)
if (IsComplexPixel())
{
ApproximateFloatComparer comparer = new(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F);
for (int i = 0; i < count; i++)
{
Assert.Equal(((IPixel)expected[i]).ToScaledVector4(), ((IPixel)actual[i]).ToScaledVector4(), comparer);
}
}
else
{
Assert.Equal(((IPixel)expected[i]).ToScaledVector4(), ((IPixel)actual[i]).ToScaledVector4(), comparer);
// SIMD and scalar conversion can select adjacent packed values at a quantization boundary.
int tolerance = Unsafe.SizeOf<TDest>() <= sizeof(ushort) ? 17 : 1;
for (int i = 0; i < count; i++)
{
Rgba32 expectedPixel = ((IPixel)expected[i]).ToRgba32();
Rgba32 actualPixel = ((IPixel)actual[i]).ToRgba32();
Assert.InRange(Math.Abs(expectedPixel.R - actualPixel.R), 0, tolerance);
Assert.InRange(Math.Abs(expectedPixel.G - actualPixel.G), 0, tolerance);
Assert.InRange(Math.Abs(expectedPixel.B - actualPixel.B), 0, tolerance);
Assert.InRange(Math.Abs(expectedPixel.A - actualPixel.A), 0, tolerance);
}
}
}
else

4
tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cfd3afda359646aa3d46e1cffbfa7060395fda4c36c419ed3a2d8865284d090d
size 4031
oid sha256:43e15b35bd282ecbf5f3b0b08b0f6fdba2e9144b0c17875d27bb8366b213c999
size 4020

4
tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c87a09b28b3f857e13a2bb420d16caa131a67c45b0fe31404756042f30de6d0
size 28139
oid sha256:a6a0e244af51f47f725dfcf1ef60705dd3223269db98685a1078b57eb2273c31
size 23988

4
tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb67006d156cff0fa8d4d1b131ac0eaa98279ae6dcc477818b2fc65f2dfb77aa
size 23396
oid sha256:d0ce3abf8024859d1375721c18049513106ade2f9529ec20864621e310ca0b1b
size 18786

4
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96729898fee87fc4305913c111a5841af205564d032b916aeb04425a20c6b22c
size 46540
oid sha256:a94178ba1f0bd2edecdfd14c93196d1e195912f98875d875e1d79c8632676d03
size 46525

4
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:53b9dabffaae6a9250bf16f201ef8c9e933327874e8be91785c4fb6cd7e787a8
size 10767
oid sha256:10b471070b26a6aedfc0fce762dd71090409494ce0728790278d8584636672a2
size 9940

4
tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d73ae8bfad75c8b169fb050653eb4f8351edf173d1cc121ff1e23e3f1e594a97
size 36342
oid sha256:c372b6317bc4806ffdbddee45ded7b3a8c9f8ee7b7a462d8ff770b60ce00e176
size 28629

Loading…
Cancel
Save