diff --git a/ImageSharp.sln b/ImageSharp.sln index 7a52040374..7e392eaa98 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -556,6 +556,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.PublicApi.Tests", "tests\ImageSharp.PublicApi.Tests\ImageSharp.PublicApi.Tests.csproj", "{7D89D21A-0F54-4B36-BEAA-2B90994E840E}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}" @@ -682,6 +684,10 @@ Global {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU + {7D89D21A-0F54-4B36-BEAA-2B90994E840E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D89D21A-0F54-4B36-BEAA-2B90994E840E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D89D21A-0F54-4B36-BEAA-2B90994E840E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D89D21A-0F54-4B36-BEAA-2B90994E840E}.Release|Any CPU.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -714,6 +720,7 @@ Global {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {7D89D21A-0F54-4B36-BEAA-2B90994E840E} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 0f28b28901..6d37fd5ce4 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -81,10 +82,13 @@ private static void SeedPixelFormats() Seed(); Seed(); + Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -95,16 +99,23 @@ private static void SeedPixelFormats() Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); Seed(); Seed(); + Seed(); Seed(); + Seed(); Seed(); + Seed(); Seed(); + Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -158,10 +169,13 @@ private static unsafe void AotCompileImage() Image img = default; img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); @@ -172,16 +186,23 @@ private static unsafe void AotCompileImage() img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); @@ -208,6 +229,7 @@ private static void AotCompileImageEncoderInternals() where TPixel : unmanaged, IPixel { default(BmpEncoderCore).Encode(default, default, default); + default(ExrEncoderCore).Encode(default, default, default); default(GifEncoderCore).Encode(default, default, default); default(JpegEncoderCore).Encode(default, default, default); default(PbmEncoderCore).Encode(default, default, default); @@ -227,6 +249,7 @@ private static void AotCompileImageDecoderInternals() where TPixel : unmanaged, IPixel { default(BmpDecoderCore).Decode(default, default, default); + default(ExrDecoderCore).Decode(default, default, default); default(GifDecoderCore).Decode(default, default, default); default(JpegDecoderCore).Decode(default, default, default); default(PbmDecoderCore).Decode(default, default, default); @@ -247,6 +270,7 @@ private static void AotCompileImageEncoders() { AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); @@ -265,6 +289,7 @@ private static void AotCompileImageDecoders() { AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); @@ -336,6 +361,7 @@ private static void AotCompileImageProcessors() AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); + AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); @@ -368,11 +394,13 @@ private static void AotCompileImageProcessors() AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); + AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); + AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index dd248d488f..85e3002333 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -21,29 +21,44 @@ namespace SixLabors.ImageSharp; { private readonly Vector4 data; private readonly IPixel? boxedHighPrecisionPixel; + private readonly bool isAssociated; + private readonly bool dataIsAssociated; /// /// Initializes a new instance of the struct. /// /// The containing the color information. + /// The alpha representation exposed by the color. + /// The alpha representation of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(Vector4 vector) + private Color(Vector4 vector, PixelAlphaRepresentation alphaRepresentation, PixelAlphaRepresentation dataAlphaRepresentation) { this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); this.boxedHighPrecisionPixel = null; + this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; + this.dataIsAssociated = dataAlphaRepresentation == PixelAlphaRepresentation.Associated; } /// /// Initializes a new instance of the struct. /// /// The pixel containing color information. + /// The alpha representation of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(IPixel pixel) + private Color(IPixel pixel, PixelAlphaRepresentation alphaRepresentation) { this.boxedHighPrecisionPixel = pixel; this.data = default; + this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; + this.dataIsAssociated = this.isAssociated; } + /// + /// Gets the alpha representation used by this color's scaled vector. + /// + public PixelAlphaRepresentation AlphaRepresentation + => this.isAssociated ? PixelAlphaRepresentation.Associated : PixelAlphaRepresentation.Unassociated; + /// /// Checks whether two structures are equal. /// @@ -80,21 +95,46 @@ public static Color FromPixel(TPixel source) { // 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) + + if (info.ComponentInfo.HasValue) { - return new Color(source.ToScaledVector4()); + int maximumComponentPrecision = info.ComponentInfo.Value.GetMaximumComponentPrecision(); + + if (maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit32) + { + if (info.AlphaRepresentation == PixelAlphaRepresentation.Associated && maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit8) + { + // Associated formats with at most eight bits per component can be canonicalized without loss by their pixel-specific conversion. + // Higher-precision formats retain their associated values because unassociation can lose representable data. + Vector4 vector = source.ToUnassociatedScaledVector4(); + return new Color(vector, info.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); + } + + return new Color(source.ToScaledVector4(), info.AlphaRepresentation, info.AlphaRepresentation); + } } - return new Color(source); + return new Color(source, info.AlphaRepresentation); } /// /// Creates a from a generic scaled . /// - /// The vector to load the pixel from. + /// The unassociated vector to load the color from. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Color FromScaledVector(Vector4 source) + => new(source, PixelAlphaRepresentation.Unassociated, PixelAlphaRepresentation.Unassociated); + + /// + /// Creates a from a generic scaled with the specified alpha representation. + /// + /// The vector to load the color from. + /// The alpha representation of . /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color FromScaledVector(Vector4 source) => new(source); + public static Color FromScaledVector(Vector4 source, PixelAlphaRepresentation alphaRepresentation) + => new(source, alphaRepresentation, alphaRepresentation); /// /// Bulk converts a span of generic scaled to a span of . @@ -103,11 +143,23 @@ public static Color FromPixel(TPixel source) /// The destination color span. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void FromScaledVector(ReadOnlySpan source, Span destination) + => FromScaledVector(source, destination, PixelAlphaRepresentation.Unassociated); + + /// + /// Bulk converts a span of generic scaled values with the specified alpha representation + /// to a span of values. + /// + /// The source vector span. + /// The destination color span. + /// The alpha representation of the source vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void FromScaledVector(ReadOnlySpan source, Span 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,19 +177,37 @@ public static void FromPixel(ReadOnlySpan source, Span de // 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) + + if (info.ComponentInfo.HasValue) { - for (int i = 0; i < source.Length; i++) + int maximumComponentPrecision = info.ComponentInfo.Value.GetMaximumComponentPrecision(); + + if (maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit32) { - destination[i] = FromScaledVector(source[i].ToScaledVector4()); + if (info.AlphaRepresentation == PixelAlphaRepresentation.Associated && maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit8) + { + // Match the scalar conversion by retaining exact unassociated values from the format-specific operation. + for (int i = 0; i < source.Length; i++) + { + Vector4 vector = source[i].ToUnassociatedScaledVector4(); + destination[i] = new Color(vector, info.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); + } + + return; + } + + for (int i = 0; i < source.Length; i++) + { + destination[i] = new Color(source[i].ToScaledVector4(), info.AlphaRepresentation, info.AlphaRepresentation); + } + + return; } } - else + + for (int i = 0; i < source.Length; i++) { - for (int i = 0; i < source.Length; i++) - { - destination[i] = new Color(source[i]); - } + destination[i] = new Color(source[i], info.AlphaRepresentation); } } @@ -276,13 +346,13 @@ public static bool TryParse(string input, out Color result, ColorHexFormat forma /// Alters the alpha channel of the color, returning a new instance. /// /// The new value of alpha [0..1]. - /// The color having it's alpha channel altered. + /// The color having its alpha channel altered. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Color WithAlpha(float alpha) { - Vector4 v = this.ToScaledVector4(); - v.W = alpha; - return FromScaledVector(v); + Vector4 vector = this.ToScaledVector4(PixelAlphaRepresentation.Unassociated); + vector.W = Numerics.Clamp(alpha, 0, 1); + return new Color(vector, this.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); } /// @@ -296,9 +366,7 @@ public Color WithAlpha(float alpha) [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(); uint hexOrder = format switch { @@ -327,29 +395,76 @@ public TPixel ToPixel() return pixel; } - if (this.boxedHighPrecisionPixel is null) + Vector4 vector = this.boxedHighPrecisionPixel?.ToScaledVector4() ?? this.data; + if (this.dataIsAssociated) { - return TPixel.FromScaledVector4(this.data); + // Preserve associated components directly while allowing the destination to quantize alpha to its own storage grid. + return TPixel.FromAssociatedScaledVector4(vector); } - return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + // Unassociated input lets an associated destination quantize alpha before it multiplies the color components. + return TPixel.FromUnassociatedScaledVector4(vector); } /// - /// Expands the color into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. + /// Expands the color into a generic ("scaled") representation, + /// preserving the , with values scaled and clamped between + /// 0 and 1. /// The vector components are typically expanded in least to greatest significance order. /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToScaledVector4() { - if (this.boxedHighPrecisionPixel is null) + Vector4 vector = this.boxedHighPrecisionPixel?.ToScaledVector4() ?? this.data; + + if (this.dataIsAssociated == this.isAssociated) + { + return vector; + } + + if (this.isAssociated) + { + Numerics.Premultiply(ref vector); + } + else + { + Numerics.UnPremultiply(ref vector); + } + + return vector; + } + + /// + /// Expands the color into a generic ("scaled") using the specified alpha representation, + /// with values scaled and clamped between 0 and 1. + /// + /// + /// The alpha representation to apply. returns color components + /// multiplied by alpha; other representations return color components independent of alpha. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4(PixelAlphaRepresentation alphaRepresentation) + { + bool targetIsAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; + Vector4 vector = this.boxedHighPrecisionPixel?.ToScaledVector4() ?? this.data; + + if (this.dataIsAssociated == targetIsAssociated) + { + return vector; + } + + if (targetIsAssociated) + { + Numerics.Premultiply(ref vector); + } + else { - return this.data; + Numerics.UnPremultiply(ref vector); } - return this.boxedHighPrecisionPixel.ToScaledVector4(); + return vector; } /// @@ -377,10 +492,11 @@ public bool Equals(Color other) { if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) { - return this.data == other.data; + return this.isAssociated == other.isAssociated && this.ToScaledVector4() == other.ToScaledVector4(); } - return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; + return this.isAssociated == other.isAssociated + && this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; } /// @@ -392,10 +508,10 @@ public override int GetHashCode() { if (this.boxedHighPrecisionPixel is null) { - return this.data.GetHashCode(); + return HashCode.Combine(this.ToScaledVector4(), this.isAssociated); } - return this.boxedHighPrecisionPixel.GetHashCode(); + return HashCode.Combine(this.boxedHighPrecisionPixel.ToScaledVector4(), this.isAssociated); } /// diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs index 066dbec3dc..0b88aa5b07 100644 --- a/src/ImageSharp/Common/Helpers/ColorNumerics.cs +++ b/src/ImageSharp/Common/Helpers/ColorNumerics.cs @@ -26,7 +26,7 @@ internal static class ColorNumerics /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels) + public static int GetBT709Luminance(Vector4 vector, int luminanceLevels) => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1)); /// diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 04ed48e210..5ffed8eb7c 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp; @@ -513,7 +514,7 @@ private static void ClampImpl(Span span, T min, T max) /// /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. /// - /// The to premultiply + /// The to premultiply. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Premultiply(ref Vector4 source) { @@ -524,50 +525,98 @@ public static void Premultiply(ref Vector4 source) } /// - /// Bulk variant of + /// Clamps associated color components to the alpha component while preserving alpha. /// - /// The span of vectors + /// The associated vector to clamp. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ClampRgbToAlpha(ref Vector4 source) + { + Vector4 alpha = PermuteW(source); + source = WithW(Vector4.Min(Vector4.Max(source, Vector4.Zero), alpha), alpha); + } + + /// + /// Premultiplies the X, Y, and Z components of each vector by its W component while preserving W. + /// + /// The vectors to premultiply. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Premultiply(Span vectors) { - if (Avx.IsSupported && vectors.Length >= 2) + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector512 vectorsEnd = ref Unsafe.Add(ref vectorsBase, (uint)(vectors.Length / vectorsPerVector)); + Vector128 alphaMask128 = Vector128.Create(0, 0, 0, -1).AsSingle(); + Vector256 alphaMask256 = Vector256.Create(alphaMask128, alphaMask128); + Vector512 alphaMask = Vector512.Create(alphaMask256, alphaMask256); + + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsEnd)) + { + Vector512 source = vectorsBase; + Vector512 alpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + + // Multiplication also squares W, so select the original W lanes to preserve alpha bit-for-bit. + vectorsBase = Vector512.ConditionalSelect(alphaMask, source, source * alpha); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); + } + + vectors = vectors[(vectors.Length - (vectors.Length % vectorsPerVector))..]; + } + + if (Vector256.IsHardwareAccelerated) { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + int vectorsPerVector = Vector256.Count / Vector128.Count; ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u); + ref Vector256 vectorsEnd = ref Unsafe.Add(ref vectorsBase, (uint)(vectors.Length / vectorsPerVector)); + Vector128 alphaMask128 = Vector128.Create(0, 0, 0, -1).AsSingle(); + Vector256 alphaMask = Vector256.Create(alphaMask128, alphaMask128); - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsEnd)) { Vector256 source = vectorsBase; - Vector256 alpha = Avx.Permute(source, ShuffleAlphaControl); - vectorsBase = Avx.Blend(Avx.Multiply(source, alpha), source, BlendAlphaControl); + Vector256 alpha = Vector256_.ShuffleNative(source, ShuffleAlphaControl); + + // Multiplication also squares W, so select the original W lanes to preserve alpha bit-for-bit. + vectorsBase = Vector256.ConditionalSelect(alphaMask, source, source * alpha); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } - if (Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - Premultiply(ref MemoryMarshal.GetReference(vectors[^1..])); - } + vectors = vectors[(vectors.Length - (vectors.Length % vectorsPerVector))..]; } - else + + if (Vector128.IsHardwareAccelerated) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, (uint)vectors.Length); + ref Vector128 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector128 vectorsEnd = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsEnd)) { - Premultiply(ref vectorsStart); + Vector128 source = vectorsBase; + Vector128 alpha = Vector128_.ShuffleNative(source, ShuffleAlphaControl); - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); + // Multiplication also squares W, so select the original W lane to preserve alpha bit-for-bit. + vectorsBase = Vector128.ConditionalSelect(alphaMask, source, source * alpha); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } + + return; + } + + ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); + + for (nuint i = 0; i < (uint)vectors.Length; i++) + { + Premultiply(ref Unsafe.Add(ref vectorsStart, i)); } } /// /// Reverses the result of premultiplying a vector via . + /// When alpha is zero, the RGB components remain unchanged because no unassociated value can be recovered. /// - /// The to premultiply + /// The to unpremultiply. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void UnPremultiply(ref Vector4 source) { @@ -575,84 +624,143 @@ public static void UnPremultiply(ref Vector4 source) UnPremultiply(ref source, alpha); } + /// + /// Unpremultiplies the X, Y, and Z components of a vector by the supplied alpha while preserving W. + /// When alpha is zero, the RGB components remain unchanged because no unassociated value can be recovered. + /// + /// The vector to unpremultiply. + /// The source alpha replicated to every component. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void UnPremultiply(ref Vector4 source, Vector4 alpha) { + // Zero alpha has no mathematical inverse, so preserve stored additive or hidden RGB data unchanged. if (alpha == Vector4.Zero) { return; } - // Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value - // Blend the result with the alpha vector to ensure that the alpha component is unchanged + // Division would replace W with one, so restore the original alpha component exactly. source = WithW(source / alpha, alpha); } /// - /// Bulk variant of + /// Unpremultiplies the X, Y, and Z components of each vector by its W component while preserving W. + /// Vectors with zero W retain their RGB components because no unassociated value can be recovered. /// - /// The span of vectors + /// The vectors to unpremultiply. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void UnPremultiply(Span vectors) { - if (Avx.IsSupported && vectors.Length >= 2) + if (Vector512.IsHardwareAccelerated) { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u); - Vector256 epsilon = Vector256.Create(Constants.Epsilon); + int vectorsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector512 vectorsEnd = ref Unsafe.Add(ref vectorsBase, (uint)(vectors.Length / vectorsPerVector)); - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsEnd)) { - Vector256 source = vectorsBase; - Vector256 alpha = Avx.Permute(source, ShuffleAlphaControl); + Vector512 source = vectorsBase; + Vector512 alpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); vectorsBase = UnPremultiply(source, alpha); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } - if (Modulo2(vectors.Length) != 0) + vectors = vectors[(vectors.Length - (vectors.Length % vectorsPerVector))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector256 vectorsEnd = ref Unsafe.Add(ref vectorsBase, (uint)(vectors.Length / vectorsPerVector)); + + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsEnd)) { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - UnPremultiply(ref MemoryMarshal.GetReference(vectors[^1..])); + Vector256 source = vectorsBase; + Vector256 alpha = Vector256_.ShuffleNative(source, ShuffleAlphaControl); + vectorsBase = UnPremultiply(source, alpha); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } + + vectors = vectors[(vectors.Length - (vectors.Length % vectorsPerVector))..]; } - else + + if (Vector128.IsHardwareAccelerated) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, (uint)vectors.Length); + ref Vector128 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector128 vectorsEnd = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length); - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsEnd)) { - UnPremultiply(ref vectorsStart); - - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); + Vector128 source = vectorsBase; + Vector128 alpha = Vector128_.ShuffleNative(source, ShuffleAlphaControl); + vectorsBase = UnPremultiply(source, alpha); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } + + return; + } + + ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); + + for (nuint i = 0; i < (uint)vectors.Length; i++) + { + UnPremultiply(ref Unsafe.Add(ref vectorsStart, i)); } } + /// + /// Unpremultiplies the RGB lanes of a vector while preserving its alpha lane. + /// When alpha is zero, the RGB lanes remain unchanged because no unassociated value can be recovered. + /// + /// The associated vector. + /// The source alpha replicated to every lane. + /// The unassociated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 UnPremultiply(Vector128 source, Vector128 alpha) + { + // Zero alpha has no mathematical inverse, so select the source lanes to preserve stored additive or hidden RGB data. + Vector128 zeroMask = Vector128.Equals(alpha, Vector128.Zero); + Vector128 result = Vector128.ConditionalSelect(zeroMask, source, source / alpha); + + // Division would replace W with one, so restore the original alpha lane exactly. + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + return Vector128.ConditionalSelect(alphaMask, alpha, result); + } + + /// + /// Unpremultiplies the RGB lanes of two vectors while preserving their alpha lanes. + /// Vectors with zero alpha retain their RGB lanes because no unassociated value can be recovered. + /// + /// The associated vectors. + /// Each source alpha replicated across its four lanes. + /// The unassociated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 UnPremultiply(Vector256 source, Vector256 alpha) { - // Check if alpha is zero to avoid division by zero + // Zero alpha has no mathematical inverse, so select the source lanes to preserve stored additive or hidden RGB data. Vector256 zeroMask = Avx.CompareEqual(alpha, Vector256.Zero); - - // Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value Vector256 result = Avx.BlendVariable(Avx.Divide(source, alpha), source, zeroMask); - // Blend the result with the alpha vector to ensure that the alpha component is unchanged + // Division would replace W with one, so restore both original alpha lanes exactly. return Avx.Blend(result, alpha, BlendAlphaControl); } + /// + /// Unpremultiplies the RGB lanes of four vectors while preserving their alpha lanes. + /// Vectors with zero alpha retain their RGB lanes because no unassociated value can be recovered. + /// + /// The associated vectors. + /// Each source alpha replicated across its four lanes. + /// The unassociated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 UnPremultiply(Vector512 source, Vector512 alpha) { - // Check if alpha is zero to avoid division by zero + // Zero alpha has no mathematical inverse, so select the source lanes to preserve stored additive or hidden RGB data. Vector512 zeroMask = Vector512.Equals(alpha, Vector512.Zero); - - // Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value Vector512 result = Vector512.ConditionalSelect(zeroMask, source, source / alpha); - // Blend the result with the alpha vector to ensure that the alpha component is unchanged + // Division would replace W with one, so restore all four original alpha lanes exactly. Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); return Vector512.ConditionalSelect(alphaMask, alpha, result); } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs index 5318ad0497..3a66be520b 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs @@ -41,11 +41,29 @@ internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, S { 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); + } + } + + /// + /// Converts byte-magnitude floating-point values to bytes using saturating round-to-nearest with midpoint values away from zero. + /// + /// The source byte magnitudes. + /// The destination bytes. + [MethodImpl(InliningOptions.ShortMethod)] + internal static void FloatToByteSaturate(ReadOnlySpan source, Span 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); } } @@ -57,22 +75,23 @@ private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan sou 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) / (float)byte.MaxValue; } } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span destination) + private static void ConvertFloatToByteRemainder(ReadOnlySpan source, Span 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); } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 022056deb0..503a64b90d 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -711,6 +711,10 @@ internal static void ByteToNormalizedFloat( ReadOnlySpan source, Span destination) { + const double reciprocal = 1D / byte.MaxValue; + const float reciprocalHigh = (float)reciprocal; + const float reciprocalLow = (float)(reciprocal - reciprocalHigh); + if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) { DebugVerifySpanInput(source, destination, Vector512.Count); @@ -719,6 +723,8 @@ internal static void ByteToNormalizedFloat( ref byte sourceBase = ref MemoryMarshal.GetReference(source); ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + Vector512 high = Vector512.Create(reciprocalHigh); + Vector512 low = Vector512.Create(reciprocalLow); for (nuint i = 0; i < n; i++) { @@ -728,11 +734,16 @@ internal static void ByteToNormalizedFloat( Vector512 i2 = Avx512F.ConvertToVector512Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)(Vector512.Count * 2))); Vector512 i3 = Avx512F.ConvertToVector512Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)(Vector512.Count * 3))); - // Declare multiplier on each line. Codegen is better. - Vector512 f0 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i0); - Vector512 f1 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i1); - Vector512 f2 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i2); - Vector512 f3 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i3); + Vector512 f0 = Avx512F.ConvertToVector512Single(i0); + Vector512 f1 = Avx512F.ConvertToVector512Single(i1); + Vector512 f2 = Avx512F.ConvertToVector512Single(i2); + Vector512 f3 = Avx512F.ConvertToVector512Single(i3); + + // The residual term restores the correctly rounded byte / 255F result without paying for vector division. + f0 = Vector512_.FusedMultiplyAdd(f0, high, f0 * low); + f1 = Vector512_.FusedMultiplyAdd(f1, high, f1 * low); + f2 = Vector512_.FusedMultiplyAdd(f2, high, f2 * low); + f3 = Vector512_.FusedMultiplyAdd(f3, high, f3 * low); ref Vector512 d = ref Unsafe.Add(ref destinationBase, i * 4); @@ -750,6 +761,8 @@ internal static void ByteToNormalizedFloat( ref byte sourceBase = ref MemoryMarshal.GetReference(source); ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + Vector256 high = Vector256.Create(reciprocalHigh); + Vector256 low = Vector256.Create(reciprocalLow); for (nuint i = 0; i < n; i++) { @@ -762,11 +775,15 @@ internal static void ByteToNormalizedFloat( ref ulong refULong = ref Unsafe.As(ref Unsafe.Add(ref sourceBase, si)); Vector256 i3 = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(Unsafe.Add(ref refULong, 3)).AsByte()); - // Declare multiplier on each line. Codegen is better. - Vector256 f0 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i0); - Vector256 f1 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i1); - Vector256 f2 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i2); - Vector256 f3 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i3); + Vector256 f0 = Avx.ConvertToVector256Single(i0); + Vector256 f1 = Avx.ConvertToVector256Single(i1); + Vector256 f2 = Avx.ConvertToVector256Single(i2); + Vector256 f3 = Avx.ConvertToVector256Single(i3); + + f0 = Vector256_.FusedMultiplyAdd(f0, high, f0 * low); + f1 = Vector256_.FusedMultiplyAdd(f1, high, f1 * low); + f2 = Vector256_.FusedMultiplyAdd(f2, high, f2 * low); + f3 = Vector256_.FusedMultiplyAdd(f3, high, f3 * low); ref Vector256 d = ref Unsafe.Add(ref destinationBase, i * 4); @@ -785,7 +802,8 @@ internal static void ByteToNormalizedFloat( ref byte sourceBase = ref MemoryMarshal.GetReference(source); ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - Vector128 scale = Vector128.Create(1 / (float)byte.MaxValue); + Vector128 high = Vector128.Create(reciprocalHigh); + Vector128 low = Vector128.Create(reciprocalLow); for (nuint i = 0; i < n; i++) { @@ -810,10 +828,15 @@ internal static void ByteToNormalizedFloat( (i2, i3) = Vector128.Widen(s1.AsInt16()); } - Vector128 f0 = scale * Vector128.ConvertToSingle(i0); - Vector128 f1 = scale * Vector128.ConvertToSingle(i1); - Vector128 f2 = scale * Vector128.ConvertToSingle(i2); - Vector128 f3 = scale * Vector128.ConvertToSingle(i3); + Vector128 f0 = Vector128.ConvertToSingle(i0); + Vector128 f1 = Vector128.ConvertToSingle(i1); + Vector128 f2 = Vector128.ConvertToSingle(i2); + Vector128 f3 = Vector128.ConvertToSingle(i3); + + f0 = Vector128_.FusedMultiplyAdd(f0, high, f0 * low); + f1 = Vector128_.FusedMultiplyAdd(f1, high, f1 * low); + f2 = Vector128_.FusedMultiplyAdd(f2, high, f2 * low); + f3 = Vector128_.FusedMultiplyAdd(f3, high, f3 * low); ref Vector128 d = ref Unsafe.Add(ref destinationBase, i * 4); @@ -834,6 +857,19 @@ internal static void ByteToNormalizedFloat( internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span destination) + => FloatToByteSaturateReduce(ref source, ref destination, byte.MaxValue); + + /// + /// Converts as many scaled floating-point values as possible to bytes and retains the unconverted remainder. + /// + /// The source buffer. + /// The destination buffer. + /// The factor applied before conversion. + [MethodImpl(InliningOptions.ShortMethod)] + internal static void FloatToByteSaturateReduce( + ref ReadOnlySpan source, + ref Span destination, + float scaleFactor) { DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); @@ -858,9 +894,10 @@ internal static void NormalizedFloatToByteSaturateReduce( if (adjustedCount > 0) { - NormalizedFloatToByteSaturate( + FloatToByteSaturate( source[..adjustedCount], - destination[..adjustedCount]); + destination[..adjustedCount], + scaleFactor); source = source[adjustedCount..]; destination = destination[adjustedCount..]; @@ -880,6 +917,18 @@ internal static void NormalizedFloatToByteSaturateReduce( internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span destination) + => FloatToByteSaturate(source, destination, byte.MaxValue); + + /// + /// Converts scaled floating-point values to bytes using saturating round-to-nearest with midpoint values away from zero. + /// + /// The source buffer. + /// The destination buffer. + /// The factor applied before conversion. + internal static void FloatToByteSaturate( + ReadOnlySpan source, + Span destination, + float scaleFactor) { if (Vector512.IsHardwareAccelerated && Avx512BW.IsSupported) { @@ -890,7 +939,7 @@ internal static void NormalizedFloatToByteSaturate( ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - Vector512 scale = Vector512.Create((float)byte.MaxValue); + Vector512 scale = Vector512.Create(scaleFactor); Vector512 mask = PermuteMaskDeinterleave16x32(); for (nuint i = 0; i < n; i++) @@ -902,10 +951,10 @@ internal static void NormalizedFloatToByteSaturate( Vector512 f2 = scale * Unsafe.Add(ref s, 2); Vector512 f3 = scale * Unsafe.Add(ref s, 3); - Vector512 w0 = Vector512_.ConvertToInt32RoundToEven(f0); - Vector512 w1 = Vector512_.ConvertToInt32RoundToEven(f1); - Vector512 w2 = Vector512_.ConvertToInt32RoundToEven(f2); - Vector512 w3 = Vector512_.ConvertToInt32RoundToEven(f3); + Vector512 w0 = Vector512_.ConvertToInt32RoundAwayFromZero(f0); + Vector512 w1 = Vector512_.ConvertToInt32RoundAwayFromZero(f1); + Vector512 w2 = Vector512_.ConvertToInt32RoundAwayFromZero(f2); + Vector512 w3 = Vector512_.ConvertToInt32RoundAwayFromZero(f3); Vector512 u0 = Avx512BW.PackSignedSaturate(w0, w1); Vector512 u1 = Avx512BW.PackSignedSaturate(w2, w3); @@ -924,7 +973,7 @@ internal static void NormalizedFloatToByteSaturate( ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - Vector256 scale = Vector256.Create((float)byte.MaxValue); + Vector256 scale = Vector256.Create(scaleFactor); Vector256 mask = PermuteMaskDeinterleave8x32(); for (nuint i = 0; i < n; i++) @@ -936,10 +985,10 @@ internal static void NormalizedFloatToByteSaturate( Vector256 f2 = scale * Unsafe.Add(ref s, 2); Vector256 f3 = scale * Unsafe.Add(ref s, 3); - Vector256 w0 = Vector256_.ConvertToInt32RoundToEven(f0); - Vector256 w1 = Vector256_.ConvertToInt32RoundToEven(f1); - Vector256 w2 = Vector256_.ConvertToInt32RoundToEven(f2); - Vector256 w3 = Vector256_.ConvertToInt32RoundToEven(f3); + Vector256 w0 = Vector256_.ConvertToInt32RoundAwayFromZero(f0); + Vector256 w1 = Vector256_.ConvertToInt32RoundAwayFromZero(f1); + Vector256 w2 = Vector256_.ConvertToInt32RoundAwayFromZero(f2); + Vector256 w3 = Vector256_.ConvertToInt32RoundAwayFromZero(f3); Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); @@ -959,7 +1008,7 @@ internal static void NormalizedFloatToByteSaturate( ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - Vector128 scale = Vector128.Create((float)byte.MaxValue); + Vector128 scale = Vector128.Create(scaleFactor); Vector128 min = Vector128.Zero; Vector128 max = Vector128.Create((int)byte.MaxValue); @@ -972,10 +1021,10 @@ internal static void NormalizedFloatToByteSaturate( Vector128 f2 = scale * Unsafe.Add(ref s, 2); Vector128 f3 = scale * Unsafe.Add(ref s, 3); - Vector128 w0 = Vector128_.ConvertToInt32RoundToEven(f0); - Vector128 w1 = Vector128_.ConvertToInt32RoundToEven(f1); - Vector128 w2 = Vector128_.ConvertToInt32RoundToEven(f2); - Vector128 w3 = Vector128_.ConvertToInt32RoundToEven(f3); + Vector128 w0 = Vector128_.ConvertToInt32RoundAwayFromZero(f0); + Vector128 w1 = Vector128_.ConvertToInt32RoundAwayFromZero(f1); + Vector128 w2 = Vector128_.ConvertToInt32RoundAwayFromZero(f2); + Vector128 w3 = Vector128_.ConvertToInt32RoundAwayFromZero(f3); w0 = Vector128_.Clamp(w0, min, max); w1 = Vector128_.Clamp(w1, min, max); diff --git a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs index a5d377eb90..ce8af4d6f2 100644 --- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs @@ -307,11 +307,37 @@ public static Vector128 ConvertToInt32RoundToEven(Vector128 vector) return Vector128.ConvertToInt32(val_2p23_f32 | sign); } + /// + /// Converts all values in to signed 32-bit integers, rounding midpoint values away from zero. + /// + /// The values to convert. + /// The converted integer values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 ConvertToInt32RoundAwayFromZero(Vector128 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 x86Adjustment = Vector128.Create(.5F) | (vector & Vector128.Create(-0F)); + return Sse2.ConvertToVector128Int32WithTruncation(vector + x86Adjustment); + } + + if (AdvSimd.IsSupported) + { + return AdvSimd.ConvertToInt32RoundAwayFromZero(vector); + } + + Vector128 sign = vector & Vector128.Create(-0F); + Vector128 fallbackAdjustment = Vector128.Create(.5F) | sign; + return Vector128.ConvertToInt32(vector + fallbackAdjustment); + } + /// /// Rounds all values in to the nearest integer /// following semantics. /// - /// The vector + /// The vector. + /// The vector with each value rounded to the nearest integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 RoundToNearestInteger(Vector128 vector) { @@ -338,30 +364,58 @@ public static Vector128 RoundToNearestInteger(Vector128 vector) } /// - /// Performs a multiplication and an addition of the . + /// Computes an estimate of ( * ) + . /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// An estimate of the multiplication and addition result. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 MultiplyAdd( - Vector128 va, - Vector128 vm0, - Vector128 vm1) + public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) { if (Fma.IsSupported) { - return Fma.MultiplyAdd(vm1, vm0, va); + return Fma.MultiplyAdd(left, right, addend); } if (AdvSimd.IsSupported) { - return AdvSimd.FusedMultiplyAdd(va, vm0, vm1); + return AdvSimd.FusedMultiplyAdd(addend, left, right); } - return va + (vm0 * vm1); + return (left * right) + addend; + } + + /// + /// Computes ( * ) + , rounded as one ternary operation. + /// + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// The fused multiplication and addition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 FusedMultiplyAdd(Vector128 left, Vector128 right, Vector128 addend) + { + if (Fma.IsSupported) + { + return Fma.MultiplyAdd(left, right, addend); + } + + if (AdvSimd.IsSupported) + { + return AdvSimd.FusedMultiplyAdd(addend, left, right); + } + + // WebAssembly SIMD has no exact fused multiply-add, so match the runtime fallback by preserving fused rounding per element. + Vector64 lower = Vector64.Create( + MathF.FusedMultiplyAdd(left.GetElement(0), right.GetElement(0), addend.GetElement(0)), + MathF.FusedMultiplyAdd(left.GetElement(1), right.GetElement(1), addend.GetElement(1))); + + Vector64 upper = Vector64.Create( + MathF.FusedMultiplyAdd(left.GetElement(2), right.GetElement(2), addend.GetElement(2)), + MathF.FusedMultiplyAdd(left.GetElement(3), right.GetElement(3), addend.GetElement(3))); + + return Vector128.Create(lower, upper); } /// diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs index 90e3169b37..1f3ebb35fa 100644 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs @@ -73,11 +73,32 @@ public static Vector256 ConvertToInt32RoundToEven(Vector256 vector) return Vector256.ConvertToInt32(val_2p23_f32 | sign); } + /// + /// Converts all values in to signed 32-bit integers, rounding midpoint values away from zero. + /// + /// The values to convert. + /// The converted integer values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ConvertToInt32RoundAwayFromZero(Vector256 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 x86Adjustment = Vector256.Create(.5F) | (vector & Vector256.Create(-0F)); + return Avx.ConvertToVector256Int32WithTruncation(vector + x86Adjustment); + } + + Vector256 sign = vector & Vector256.Create(-0F); + Vector256 fallbackAdjustment = Vector256.Create(.5F) | sign; + return Vector256.ConvertToInt32(vector + fallbackAdjustment); + } + /// /// Rounds all values in to the nearest integer /// following semantics. /// - /// The vector + /// The vector. + /// The vector with each value rounded to the nearest integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 RoundToNearestInteger(Vector256 vector) { @@ -94,25 +115,46 @@ public static Vector256 RoundToNearestInteger(Vector256 vector) } /// - /// Performs a multiplication and an addition of the . + /// Computes an estimate of ( * ) + . /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// An estimate of the multiplication and addition result. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyAdd( - Vector256 va, - Vector256 vm0, - Vector256 vm1) + public static Vector256 MultiplyAddEstimate(Vector256 left, Vector256 right, Vector256 addend) { if (Fma.IsSupported) { - return Fma.MultiplyAdd(vm0, vm1, va); + return Fma.MultiplyAdd(left, right, addend); } - return va + (vm0 * vm1); + Vector128 lower = Vector128_.MultiplyAddEstimate(left.GetLower(), right.GetLower(), addend.GetLower()); + Vector128 upper = Vector128_.MultiplyAddEstimate(left.GetUpper(), right.GetUpper(), addend.GetUpper()); + + return Vector256.Create(lower, upper); + } + + /// + /// Computes ( * ) + , rounded as one ternary operation. + /// + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// The fused multiplication and addition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 FusedMultiplyAdd(Vector256 left, Vector256 right, Vector256 addend) + { + if (Fma.IsSupported) + { + return Fma.MultiplyAdd(left, right, addend); + } + + // Match the runtime fallback by recursively applying the same fused contract to both halves. + Vector128 lower = Vector128_.FusedMultiplyAdd(left.GetLower(), right.GetLower(), addend.GetLower()); + Vector128 upper = Vector128_.FusedMultiplyAdd(left.GetUpper(), right.GetUpper(), addend.GetUpper()); + + return Vector256.Create(lower, upper); } /// diff --git a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs index 82a20158ae..0193a51bac 100644 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs @@ -59,33 +59,75 @@ public static Vector512 ShuffleNative(Vector512 vector, Vector512 ConvertToInt32RoundToEven(Vector512 vector) => Avx512F.ConvertToVector512Int32(vector); + /// + /// Converts all values in to signed 32-bit integers, rounding midpoint values away from zero. + /// + /// The values to convert. + /// The converted integer values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ConvertToInt32RoundAwayFromZero(Vector512 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 half = Vector512.Create(.5F) | (vector & Vector512.Create(-0F)); + return Avx512F.ConvertToVector512Int32WithTruncation(vector + half); + } + /// /// Rounds all values in to the nearest integer /// following semantics. /// - /// The vector + /// The vector. + /// The vector with each value rounded to the nearest integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 RoundToNearestInteger(Vector512 vector) - // imm8 = 0b1000: - // imm8[7:4] = 0b0000 -> preserve 0 fractional bits (round to whole numbers) - // imm8[3:0] = 0b1000 -> _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC (round to nearest even, suppress exceptions) - => Avx512F.RoundScale(vector, 0b0000_1000); + // imm8 = 0b1000: + // imm8[7:4] = 0b0000 -> preserve 0 fractional bits (round to whole numbers) + // imm8[3:0] = 0b1000 -> _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC (round to nearest even, suppress exceptions) + => Avx512F.RoundScale(vector, 0b0000_1000); /// - /// Performs a multiplication and an addition of the . + /// Computes an estimate of ( * ) + . /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// An estimate of the multiplication and addition result. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 MultiplyAdd( - Vector512 va, - Vector512 vm0, - Vector512 vm1) - => Avx512F.FusedMultiplyAdd(vm0, vm1, va); + public static Vector512 MultiplyAddEstimate(Vector512 left, Vector512 right, Vector512 addend) + { + if (Avx512F.IsSupported) + { + return Avx512F.FusedMultiplyAdd(left, right, addend); + } + + Vector256 lower = Vector256_.MultiplyAddEstimate(left.GetLower(), right.GetLower(), addend.GetLower()); + Vector256 upper = Vector256_.MultiplyAddEstimate(left.GetUpper(), right.GetUpper(), addend.GetUpper()); + + return Vector512.Create(lower, upper); + } + + /// + /// Computes ( * ) + , rounded as one ternary operation. + /// + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// The fused multiplication and addition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 FusedMultiplyAdd(Vector512 left, Vector512 right, Vector512 addend) + { + if (Avx512F.IsSupported) + { + return Avx512F.FusedMultiplyAdd(left, right, addend); + } + + // Match the runtime fallback by recursively applying the same fused contract to both halves. + Vector256 lower = Vector256_.FusedMultiplyAdd(left.GetLower(), right.GetLower(), addend.GetLower()); + Vector256 upper = Vector256_.FusedMultiplyAdd(left.GetUpper(), right.GetUpper(), addend.GetUpper()); + + return Vector512.Create(lower, upper); + } /// /// Performs a multiplication and a negated addition of the . diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 519b0d536d..aba1243d77 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -350,10 +350,10 @@ private void ReadRle(BufferedReadStream stream, BmpCompression compressi pixelRow[x] = this.rleSkippedPixelHandling switch { RleSkippedPixelHandling.FirstColorOfPalette => TPixel.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])), - RleSkippedPixelHandling.Transparent => TPixel.FromScaledVector4(Vector4.Zero), + RleSkippedPixelHandling.Transparent => TPixel.FromUnassociatedScaledVector4(Vector4.Zero), // Default handling for skipped pixels is black (which is what System.Drawing is also doing). - _ => TPixel.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), + _ => TPixel.FromUnassociatedScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), }; } else @@ -411,10 +411,10 @@ private void ReadRle24(BufferedReadStream stream, Buffer2D pixel pixelRow[x] = this.rleSkippedPixelHandling switch { RleSkippedPixelHandling.FirstColorOfPalette => TPixel.FromBgr24(Unsafe.As(ref bufferSpan[idx])), - RleSkippedPixelHandling.Transparent => TPixel.FromScaledVector4(Vector4.Zero), + RleSkippedPixelHandling.Transparent => TPixel.FromUnassociatedScaledVector4(Vector4.Zero), // Default handling for skipped pixels is black (which is what System.Drawing is also doing). - _ => TPixel.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), + _ => TPixel.FromUnassociatedScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), }; } else @@ -1272,7 +1272,7 @@ private void ReadRgb32BitFields(BufferedReadStream stream, Buffer2D( for (int y = 0; y < region.Height; y++) { Span span = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplaceTransparentPixels(vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index c24352d576..abe3ae439e 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using System.Text; using SixLabors.ImageSharp.Formats.Exr.Compression; @@ -208,8 +209,10 @@ private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buf for (int x = 0; x < width; x++) { - HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); - pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); + Vector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1F); + + // OpenEXR channels are associated color values, not values in the destination pixel format's native numeric range. + pixelRow[x] = TPixel.FromAssociatedScaledVector4(pixelValue); } decodedRows++; @@ -288,7 +291,9 @@ private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffe for (int x = 0; x < width; x++) { Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); - pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); + + // Rgba128 normalizes the unsigned channel values before the destination applies its own numeric representation. + pixelRow[x] = TPixel.FromAssociatedScaledVector4(pixelValue.ToVector4()); } decodedRows++; @@ -644,7 +649,7 @@ private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) this.Channels = this.HeaderAttributes.Channels; this.Compression = this.HeaderAttributes.Compression; uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); - long chunkCount = (this.Height + (long)rowsPerBlock - 1) / rowsPerBlock; + long chunkCount = (this.Height + rowsPerBlock - 1) / rowsPerBlock; long offsetTableByteCount = chunkCount * sizeof(ulong); // The scanline offset table sits between the header and pixel chunks; proving it diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 81b7c4da11..bd74a46b77 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -206,7 +206,9 @@ private ulong[] EncodeFloatingPointPixelData( Span pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); for (int x = 0; x < width; x++) { - Vector4 vector4 = pixelRowSpan[x].ToVector4(); + // OpenEXR stores RGB associated with alpha. Use the native vector domain so floating-point and HDR component + // ranges are preserved instead of being clamped through the scaled [0, 1] representation. + Vector4 vector4 = pixelRowSpan[x].ToAssociatedVector4(); redBuffer[x] = vector4.X; greenBuffer[x] = vector4.Y; blueBuffer[x] = vector4.Z; @@ -303,7 +305,8 @@ private ulong[] EncodeUnsignedIntPixelData( Span pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); for (int x = 0; x < width; x++) { - Vector4 vector4 = pixelRowSpan[x].ToVector4(); + // OpenEXR channels use associated alpha; the native vector conversion also preserves the integer channel range. + Vector4 vector4 = pixelRowSpan[x].ToAssociatedVector4(); rgb = Rgba128.FromVector4(vector4); redBuffer[x] = rgb.R; diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index 22762289ce..de1988674e 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -53,7 +53,8 @@ public PixelTypeInfo GetPixelTypeInfo() bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; } - PixelAlphaRepresentation alpha = hasAlpha ? PixelAlphaRepresentation.Unassociated : PixelAlphaRepresentation.None; + // OpenEXR defines RGBA color channels as premultiplied by alpha, so expose the association stored by the format. + PixelAlphaRepresentation alpha = hasAlpha ? PixelAlphaRepresentation.Associated : PixelAlphaRepresentation.None; PixelColorType color = PixelColorType.RGB; int componentsCount = 0; diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 344c13cc94..d4a6be000a 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -541,7 +541,7 @@ private static int GetTransparentIndex(IndexedImageFrame? quanti int index = -1; if (quantized != null) { - TPixel transparentPixel = TPixel.FromScaledVector4(Vector4.Zero); + TPixel transparentPixel = TPixel.FromUnassociatedScaledVector4(Vector4.Zero); ReadOnlySpan palette = quantized.Palette.Span; // Transparent pixels are much more likely to be found at the end of a palette. diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs index 633080706b..3c4a64f806 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs @@ -74,7 +74,7 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane ref Vector128 b = ref Unsafe.Add(ref srcBlue, i); // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); + Unsafe.Add(ref destLuminance, i) = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs index 0b2b8a119f..3d98d1effc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs @@ -74,7 +74,7 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); + Unsafe.Add(ref destLuminance, i) = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs index 0a58b0196b..96126ac9da 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs @@ -74,7 +74,7 @@ protected override void ConvertFromRgbVectorized(in ComponentValues values, Span ref Vector512 b = ref Unsafe.Add(ref srcBlue, i); // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); + Unsafe.Add(ref destLuminance, i) = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs index b360c373ad..ae3391d42a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs @@ -53,9 +53,9 @@ public override void ConvertToRgbInPlace(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAdd(y, cr, rCrMult) * scaledK; - Vector128 g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK; - Vector128 b = Vector128_.MultiplyAdd(y, cb, bCbMult) * scaledK; + Vector128 r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; + Vector128 g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; + Vector128 b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; c0 = r; c1 = g; @@ -117,9 +117,9 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector128 cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector128 cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector128 y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector128 cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector128 cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y * maxSampleValue; Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs index f996522d36..ec6b228ca4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs @@ -53,9 +53,9 @@ public override void ConvertToRgbInPlace(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAdd(y, cr, rCrMult) * scaledK; - Vector256 g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK; - Vector256 b = Vector256_.MultiplyAdd(y, cb, bCbMult) * scaledK; + Vector256 r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; + Vector256 g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; + Vector256 b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; c0 = r; c1 = g; @@ -117,9 +117,9 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector256 cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector256 cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector256 y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector256 cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector256 cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y * maxSampleValue; Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs index 47168a739d..6a0fb93e27 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs @@ -57,9 +57,9 @@ protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAdd(y, cr, rCrMult) * scaledK; - Vector512 g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK; - Vector512 b = Vector512_.MultiplyAdd(y, cb, bCbMult) * scaledK; + Vector512 r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; + Vector512 g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; + Vector512 b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; c0 = r; c1 = g; @@ -128,9 +128,9 @@ internal static void ConvertFromRgbVectorized(in ComponentValues values, float m // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector512 cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector512 cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector512 y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector512 cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector512 cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y * maxSampleValue; Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs index 01c8508edc..37847a6e67 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs @@ -53,9 +53,9 @@ public override void ConvertToRgbInPlace(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAdd(y, cr, rCrMult); - Vector128 g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector128 b = Vector128_.MultiplyAdd(y, cb, bCbMult); + Vector128 r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y); + Vector128 g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y); r = Vector128_.RoundToNearestInteger(r) * scale; g = Vector128_.RoundToNearestInteger(g) * scale; @@ -108,9 +108,9 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector128 cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector128 cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector128 y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector128 cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector128 cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y; Unsafe.Add(ref destCb, i) = cb; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs index 8fdf1004d8..fbccf88e20 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs @@ -53,9 +53,9 @@ public override void ConvertToRgbInPlace(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAdd(y, cr, rCrMult); - Vector256 g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = Vector256_.MultiplyAdd(y, cb, bCbMult); + Vector256 r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y); + Vector256 g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y); r = Vector256_.RoundToNearestInteger(r) * scale; g = Vector256_.RoundToNearestInteger(g) * scale; @@ -108,9 +108,9 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector256 cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector256 cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector256 y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector256 cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector256 cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y; Unsafe.Add(ref destCb, i) = cb; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs index 33cb2496c6..3879171752 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs @@ -56,9 +56,9 @@ protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAdd(y, cr, rCrMult); - Vector512 g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector512 b = Vector512_.MultiplyAdd(y, cb, bCbMult); + Vector512 r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y); + Vector512 g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y); r = Vector512_.RoundToNearestInteger(r) * scale; g = Vector512_.RoundToNearestInteger(g) * scale; @@ -107,9 +107,9 @@ protected override void ConvertFromRgbVectorized(in ComponentValues values, Span // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector512 cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector512 cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector512 y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector512 cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector512 cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y; Unsafe.Add(ref destCb, i) = cb; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs index 67b7ee0dc6..279c100b2c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs @@ -58,9 +58,9 @@ public override void ConvertToRgbInPlace(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAdd(y, cr, rCrMult); - Vector128 g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector128 b = Vector128_.MultiplyAdd(y, cb, bCbMult); + Vector128 r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y); + Vector128 g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y); r = max - Vector128_.RoundToNearestInteger(r); g = max - Vector128_.RoundToNearestInteger(g); @@ -122,9 +122,9 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector128 cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector128 cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector128 y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector128 cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector128 cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y; Unsafe.Add(ref destCb, i) = cb; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs index 3a586d25fa..1bebdfcf44 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs @@ -58,9 +58,9 @@ public override void ConvertToRgbInPlace(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAdd(y, cr, rCrMult); - Vector256 g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = Vector256_.MultiplyAdd(y, cb, bCbMult); + Vector256 r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y); + Vector256 g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y); r = max - Vector256_.RoundToNearestInteger(r); g = max - Vector256_.RoundToNearestInteger(g); @@ -122,9 +122,9 @@ public override void ConvertFromRgb(in ComponentValues values, Span rLane // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector256 cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector256 cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector256 y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector256 cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector256 cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y; Unsafe.Add(ref destCb, i) = cb; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs index b81a833cde..9c0e1ab747 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs @@ -58,9 +58,9 @@ protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAdd(y, cr, rCrMult); - Vector512 g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector512 b = Vector512_.MultiplyAdd(y, cb, bCbMult); + Vector512 r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y); + Vector512 g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y); r = max - Vector512_.RoundToNearestInteger(r); g = max - Vector512_.RoundToNearestInteger(g); @@ -126,9 +126,9 @@ protected override void ConvertFromRgbVectorized(in ComponentValues values, Span // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector512 cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector512 cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); + Vector512 y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); + Vector512 cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); + Vector512 cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); Unsafe.Add(ref destY, i) = y; Unsafe.Add(ref destCb, i) = cb; diff --git a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs index bcd8c70431..7c342d7a0b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs @@ -55,8 +55,8 @@ static void FDCT8x8_1D_Vector256(ref Block8x8F block) tmp12 = tmp6 + tmp7; Vector256 z5 = (tmp10 - tmp12) * Vector256.Create(0.382683433f); // mm256_F_0_3826 - Vector256 z2 = Vector256_.MultiplyAdd(z5, Vector256.Create(0.541196100f), tmp10); // mm256_F_0_5411 - Vector256 z4 = Vector256_.MultiplyAdd(z5, Vector256.Create(1.306562965f), tmp12); // mm256_F_1_3065 + Vector256 z2 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.541196100f), tmp10, z5); // mm256_F_0_5411 + Vector256 z4 = Vector256_.MultiplyAddEstimate(Vector256.Create(1.306562965f), tmp12, z5); // mm256_F_1_3065 Vector256 z3 = tmp11 * mm256_F_0_7071; Vector256 z11 = tmp7 + z3; @@ -122,8 +122,8 @@ static void IDCT8x8_1D_Vector256(ref Block8x8F block) z5 = (z10 + z12) * Vector256.Create(1.847759065f); // mm256_F_1_8477 - tmp10 = Vector256_.MultiplyAdd(z5, z12, Vector256.Create(-1.082392200f)); // mm256_F_n1_0823 - tmp12 = Vector256_.MultiplyAdd(z5, z10, Vector256.Create(-2.613125930f)); // mm256_F_n2_6131 + tmp10 = Vector256_.MultiplyAddEstimate(z12, Vector256.Create(-1.082392200f), z5); // mm256_F_n1_0823 + tmp12 = Vector256_.MultiplyAddEstimate(z10, Vector256.Create(-2.613125930f), z5); // mm256_F_n2_6131 tmp6 = tmp12 - tmp7; tmp5 = tmp11 - tmp6; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index d818aef1b0..16c3cf1163 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -34,7 +34,7 @@ public BlackIsZero16TiffColor(Configuration configuration, bool isBigEndian) public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { L16 l16 = TiffUtilities.L16Default; - TPixel color = TPixel.FromScaledVector4(Vector4.Zero); + TPixel color = TPixel.FromUnassociatedScaledVector4(Vector4.Zero); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs index ac316459d8..730091b6ab 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -40,7 +40,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float intensity = BitConverter.ToSingle(buffer); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); } } else @@ -50,7 +50,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float intensity = BitConverter.ToSingle(data.Slice(offset, 4)); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index 7428ef0577..afc9bb26f1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -36,7 +36,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in { int value = bitReader.ReadBits(this.bitsPerSample0); float intensity = value / this.factor; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); } bitReader.NextRow(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs index b422e65adc..0fb7057aa2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs @@ -109,7 +109,7 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, // Convert CIE Lab -> Rgb -> Vector4 -> TPixel this.colorProfileConverter.Convert(cieLabRow, rgbRow); Rgb.ToScaledVector4(rgbRow, vectorRow); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } return; @@ -138,7 +138,7 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, // Convert CIE Lab -> Rgb -> Vector4 -> TPixel this.colorProfileConverter.Convert(cieLabRow, rgbRow); Rgb.ToScaledVector4(rgbRow, vectorRow); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs index 4455753bf3..5009141096 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs @@ -105,7 +105,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in // Convert CIE Lab -> Rgb -> Vector4 -> TPixel this.colorProfileConverter.Convert(cieLabRow, rgbRow); Rgb.ToScaledVector4(rgbRow, vectorRow); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } return; @@ -134,7 +134,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in // Convert CIE Lab -> Rgb -> Vector4 -> TPixel this.colorProfileConverter.Convert(cieLabRow, rgbRow); Rgb.ToScaledVector4(rgbRow, vectorRow); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs index 1252f1d3d2..b7ad55cb10 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, { CieLab lab = new((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]); Rgb rgb = ColorProfileConverter.Convert(in lab); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); offset++; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs index ce5bed53c4..7e71cab820 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs @@ -39,7 +39,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float l = (data[offset] & 0xFF) * 100f * Inv255; CieLab lab = new(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); Rgb rgb = ColorProfileConverter.Convert(in lab); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1f)); offset += 3; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs index 509056267b..d8e45c25c5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs @@ -66,7 +66,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { - pixelRow[x] = TPixel.FromVector4(new Vector4(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, 1.0f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, 1.0f)); offset += 3; } @@ -99,7 +99,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in // Convert CMYK -> RGB -> Vector4 -> TPixel this.colorProfileConverter.Convert(cmykRow, rgbRow); Rgb.ToScaledVector4(rgbRow, vectorRow); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index d1b519bddc..52315a02b4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -97,7 +97,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in Vector4 color = this.vectorPallete[index]; color.W = alpha; - pixelRow[x] = TPixel.FromScaledVector4(color); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(color); // Best-effort palette update for downstream conversions. // This is intentionally "last writer wins" with no per-pixel branch. @@ -163,7 +163,7 @@ private static TPixel[] GeneratePixelPalette(ushort[] colorMap, int colorCount) float r = colorMap[rOffset + i] * InvMax; float g = colorMap[gOffset + i] * InvMax; float b = colorMap[bOffset + i] * InvMax; - palette[i] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + palette[i] = TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f)); } return palette; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs index 3a90e81746..f84a77939b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs @@ -30,7 +30,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in byte b = (byte)((data[offset] & 0xF0) >> 4); Bgra4444 bgra = new() { PackedValue = ToBgraPackedValue(b, g, r) }; - pixelRow[x] = TPixel.FromScaledVector4(bgra.ToScaledVector4()); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(bgra.ToUnassociatedScaledVector4()); if (x + 1 >= pixelRow.Length) { offset++; @@ -44,7 +44,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in offset++; bgra.PackedValue = ToBgraPackedValue(b, g, r); - pixelRow[x + 1] = TPixel.FromScaledVector4(bgra.ToScaledVector4()); + pixelRow[x + 1] = TPixel.FromUnassociatedScaledVector4(bgra.ToUnassociatedScaledVector4()); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs index f9c17eb2f5..f48879500c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -51,7 +51,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float b = BitConverter.ToSingle(buffer); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f)); } } else @@ -67,7 +67,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float b = BitConverter.ToSingle(data.Slice(offset, 4)); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f)); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index a013abfbdb..33f1703ae0 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -63,7 +63,7 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f)); } rBitReader.NextRow(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 3c205d1476..0578cbc580 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -52,7 +52,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f)); } bitReader.NextRow(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs index 086aa3b4e1..015b313832 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Numerics; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -96,16 +97,21 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length * 8; - PixelOperations.Instance.FromRgba64Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - if (hasAssociatedAlpha) { - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); + // This little-endian branch can view the interleaved samples as Rgba64. The values are already associated, so + // the Rgba64 operation performs only unpacking and normalization before the destination consumes that representation. + ReadOnlySpan associatedSamples = MemoryMarshal.Cast(data.Slice(offset, byteCount)); + PixelOperations.Instance.ToVector4(this.configuration, associatedSamples, vectorsSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + } + else + { + PixelOperations.Instance.FromRgba64Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); } offset += byteCount; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs index 26ffbbab9b..65bfe7431b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Numerics; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -41,16 +42,22 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length * 4; - PixelOperations.Instance.FromRgba32Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); if (hasAssociatedAlpha) { - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); + // The TIFF samples already contain associated RGB. Reinterpret them as Rgba32 only to normalize and unpack the + // channels; asking that unassociated storage type to associate them would multiply RGB a second time. + ReadOnlySpan associatedSamples = MemoryMarshal.Cast(data.Slice(offset, byteCount)); + PixelOperations.Instance.ToVector4(this.configuration, associatedSamples, vectorsSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + } + else + { + PixelOperations.Instance.FromRgba32Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); } offset += byteCount; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs index 87a0196413..2784caa596 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs @@ -16,16 +16,24 @@ internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder { private readonly bool isBigEndian; + private readonly TiffExtraSampleType? extraSamplesType; + /// /// Initializes a new instance of the class. /// /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// The alpha representation declared by the TIFF extra samples field. + public RgbaFloat32323232TiffColor(bool isBigEndian, TiffExtraSampleType? extraSamplesType) + { + this.isBigEndian = isBigEndian; + this.extraSamplesType = extraSamplesType; + } /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { int offset = 0; + bool hasAssociatedAlpha = this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; Span buffer = stackalloc byte[4]; for (int y = top; y < top + height; y++) @@ -56,7 +64,10 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float a = BitConverter.ToSingle(buffer); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, a)); + Vector4 vector = new(r, g, b, a); + pixelRow[x] = hasAssociatedAlpha + ? TPixel.FromAssociatedScaledVector4(vector) + : TPixel.FromUnassociatedScaledVector4(vector); } } else @@ -75,7 +86,10 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float a = BitConverter.ToSingle(data.Slice(offset, 4)); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, a)); + Vector4 vector = new(r, g, b, a); + pixelRow[x] = hasAssociatedAlpha + ? TPixel.FromAssociatedScaledVector4(vector) + : TPixel.FromUnassociatedScaledVector4(vector); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs index 7a599a06a7..c505b58d3a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs @@ -80,11 +80,11 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, Vector4 vector = new(r, g, b, a); if (hasAssociatedAlpha) { - pixelRow[x] = TiffUtilities.UnPremultiply(ref vector); + pixelRow[x] = TPixel.FromAssociatedScaledVector4(vector); } else { - pixelRow[x] = TPixel.FromScaledVector4(vector); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(vector); } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs index 68b59c95a4..2c934036af 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs @@ -63,16 +63,16 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; + float a = bitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; Vector4 vector = new(r, g, b, a); if (hasAssociatedAlpha) { - pixelRow[x] = TiffUtilities.UnPremultiply(ref vector); + pixelRow[x] = TPixel.FromAssociatedScaledVector4(vector); } else { - pixelRow[x] = TPixel.FromScaledVector4(vector); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(vector); } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index dd36e912cf..bb109de777 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -383,7 +383,7 @@ public static TiffBaseColorDecoder Create( && bitsPerSample.Channel0 == 32, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaFloat32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + return new RgbaFloat32323232TiffColor(byteOrder == ByteOrder.BigEndian, extraSampleType); case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs index 6986f25ebd..7fe142d741 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs @@ -40,7 +40,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float intensity = 1.0f - BitConverter.ToSingle(buffer); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); } } else @@ -50,7 +50,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in float intensity = 1.0f - BitConverter.ToSingle(data.Slice(offset, 4)); offset += 4; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 0cd01a6199..91dcf72e56 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -36,7 +36,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in { int value = bitReader.ReadBits(this.bitsPerSample0); float intensity = 1f - (value / this.factor); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); } bitReader.NextRow(); diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs index b7d412f3c8..3bc4500fd7 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs @@ -40,72 +40,67 @@ internal static class TiffUtilities public static TPixel ColorFromRgba64Premultiplied(ushort r, ushort g, ushort b, ushort a) where TPixel : unmanaged, IPixel { + // TIFF 6.0 requires associated RGB to be interpreted as zero when alpha is zero. Enforce that codec contract here + // while leaving the general associated-alpha conversion contract free to preserve hidden RGB for formats such as EXR. if (a == 0) { - return TPixel.FromRgba64(default); + return TPixel.FromAssociatedScaledVector4(Vector4.Zero); } - float scale = 65535f / a; - ushort ur = (ushort)Math.Min(r * scale, 65535); - ushort ug = (ushort)Math.Min(g * scale, 65535); - ushort ub = (ushort)Math.Min(b * scale, 65535); - - return TPixel.FromRgba64(new Rgba64(ur, ug, ub, a)); + // TIFF marks these samples as already associated. Passing that representation directly avoids an unassociate/reassociate + // round trip for associated destinations while unassociated formats perform the required conversion in their pixel contract. + return TPixel.FromAssociatedScaledVector4(new Vector4(r, g, b, a) / ushort.MaxValue); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24Bit(uint r, uint g, uint b) where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, 1f) * Scale24BitVector); + => TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f) * Scale24BitVector); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24Bit(uint r, uint g, uint b, uint a) where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, a) * Scale24Bit); + => TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, a) * Scale24Bit); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24BitPremultiplied(uint r, uint g, uint b, uint a) where TPixel : unmanaged, IPixel { - Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; - return UnPremultiply(ref colorVector); + // TIFF 6.0 assigns no color to a fully transparent associated sample, even when malformed input stores nonzero RGB. + return a == 0 + ? TPixel.FromAssociatedScaledVector4(Vector4.Zero) + : TPixel.FromAssociatedScaledVector4(new Vector4(r, g, b, a) * Scale24Bit); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(uint r, uint g, uint b) where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, 1f) * Scale32BitVector); + => TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, 1f) * Scale32BitVector); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(uint r, uint g, uint b, uint a) where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, a) * Scale32Bit); + => TPixel.FromUnassociatedScaledVector4(new Vector4(r, g, b, a) * Scale32Bit); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32BitPremultiplied(uint r, uint g, uint b, uint a) where TPixel : unmanaged, IPixel { - Vector4 vector = new Vector4(r, g, b, a) * Scale32Bit; - return UnPremultiply(ref vector); + // TIFF 6.0 assigns no color to a fully transparent associated sample, even when malformed input stores nonzero RGB. + return a == 0 + ? TPixel.FromAssociatedScaledVector4(Vector4.Zero) + : TPixel.FromAssociatedScaledVector4(new Vector4(r, g, b, a) * Scale32Bit); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24Bit(uint intensity) where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale24BitVector); + => TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale24BitVector); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(uint intensity) where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale32BitVector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel UnPremultiply(ref Vector4 vector) - where TPixel : unmanaged, IPixel - { - Numerics.UnPremultiply(ref vector); - return TPixel.FromScaledVector4(vector); - } + => TPixel.FromUnassociatedScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale32BitVector); /// /// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value. diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 32a5f0073e..9f545fc2a2 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -141,6 +141,16 @@ True DefaultPixelBlenders.Generated.tt + + True + True + AssociatedAlphaPixelBlenders.Generated.tt + + + True + True + AssociatedAlphaPorterDuffFunctions.Generated.tt + True True @@ -221,6 +231,14 @@ DefaultPixelBlenders.Generated.cs TextTemplatingFileGenerator + + AssociatedAlphaPixelBlenders.Generated.cs + TextTemplatingFileGenerator + + + AssociatedAlphaPorterDuffFunctions.Generated.cs + TextTemplatingFileGenerator + TextTemplatingFileGenerator ImageExtensions.Save.cs diff --git a/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs new file mode 100644 index 0000000000..ad26eb4e61 --- /dev/null +++ b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs @@ -0,0 +1,387 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles.Companding; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides bulk operations for pixel formats that store associated alpha. +/// +/// The associated-alpha pixel format. +public abstract class AssociatedAlphaPixelOperations : PixelOperations + where TPixel : unmanaged, IPixel +{ + /// + public override PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + => AssociatedAlphaPixelBlenders.GetPixelBlender(colorMode, alphaMode); + + /// + protected abstract override void ToUnassociatedVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void ToAssociatedVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void FromUnassociatedVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void FromAssociatedVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void ToUnassociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void ToAssociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void FromUnassociatedScaledVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination); + + /// + public override void From( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + if (source.IsEmpty) + { + return; + } + + // Cap large conversions at 1,024 vectors while avoiding a 16 KiB rental for short spans. + int sliceLength = Math.Min(source.Length, 1024); + int numberOfSlices = source.Length / sliceLength; + + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan()[..sliceLength]; + + // 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 sourceSlice = source.Slice(start, sliceLength); + Span destinationSlice = destination.Slice(start, sliceLength); + PixelOperations.Instance.ToVector4( + configuration, + sourceSlice, + vectorSpan, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + this.FromUnassociatedScaledVector4Destructive(configuration, vectorSpan, destinationSlice); + } + + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = source.Length - endOfCompleteSlices; + + if (remainder > 0) + { + ReadOnlySpan sourceSlice = source[endOfCompleteSlices..]; + Span destinationSlice = destination.Slice(endOfCompleteSlices, remainder); + vectorSpan = vectorSpan[..remainder]; + PixelOperations.Instance.ToVector4( + configuration, + sourceSlice, + vectorSpan, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + this.FromUnassociatedScaledVector4Destructive(configuration, vectorSpan, destinationSlice); + } + } + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply) || !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Transfer functions operate on straight color components. Associated input must therefore be unassociated before companding. + if (associated) + { + Numerics.UnPremultiply(sourceVectors); + } + + SRgbCompanding.Compress(sourceVectors); + + if (scaled) + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.FromAssociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + } + else if (associated) + { + this.FromAssociatedVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationVectors, nameof(destinationVectors)); + + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply) || !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Extract straight color before applying the transfer function; companding associated components would make RGB depend on alpha. + if (scaled) + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } + + Span converted = destinationVectors[..source.Length]; + SRgbCompanding.Expand(converted); + + if (associated) + { + Numerics.Premultiply(converted); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.ToAssociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + } + else if (associated) + { + this.ToAssociatedVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } + } + + /// + public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + /// Converts associated source pixels to an unassociated destination format. + /// + /// The destination pixel format. + /// The configuration. + /// The source pixels. + /// The destination pixels. + private void ConvertToUnassociated( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TDestinationPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + if (source.IsEmpty) + { + return; + } + + int sliceLength = Math.Min(source.Length, 1024); + int numberOfSlices = source.Length / sliceLength; + + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan()[..sliceLength]; + PixelOperations destinationOperations = PixelOperations.Instance; + + // Generated destination dispatch routes conversion back through this source operation's ToX override. Extract through this + // operation's protected bulk hook, then use the destination's public modifier contract to avoid recursive dispatch. + for (int i = 0; i < numberOfSlices; i++) + { + int start = i * sliceLength; + ReadOnlySpan sourceSlice = source.Slice(start, sliceLength); + Span destinationSlice = destination.Slice(start, sliceLength); + this.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); + destinationOperations.FromVector4Destructive(configuration, vectorSpan, destinationSlice, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + } + + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = source.Length - endOfCompleteSlices; + + if (remainder > 0) + { + ReadOnlySpan sourceSlice = source[endOfCompleteSlices..]; + Span destinationSlice = destination.Slice(endOfCompleteSlices, remainder); + vectorSpan = vectorSpan[..remainder]; + this.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); + destinationOperations.FromVector4Destructive(configuration, vectorSpan, destinationSlice, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + } + } +} diff --git a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs index 02936f1602..53f22eb09a 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; @@ -10,6 +12,27 @@ namespace SixLabors.ImageSharp.PixelFormats; /// internal static class HalfTypeHelper { + // IEEE 754 binary16 has a largest finite magnitude of 65504. Scaled pixel vectors map that complete finite + // interval to [0, 1], while native vectors continue to expose the stored floating-point value directly. + internal const float FiniteMinimum = -65504F; + internal const float FiniteMaximum = 65504F; + internal const float FiniteRange = FiniteMaximum - FiniteMinimum; + internal const float InverseFiniteRange = (float)(1D / FiniteRange); + internal const float ScaledMidpoint = .5F; + + // These constants mirror the binary16 conversion used by System.Half. Keeping the vector conversion + // bit-for-bit equivalent to the scalar runtime conversion makes SIMD a pure throughput optimization. + private const uint HalfExponentMask = 0x7C00; + private const uint HalfSignMask = 0x8000; + private const uint HalfToSingleBitsMask = 0x0FFF_E000; + private const uint SingleExponentLowerBound = 0x3880_0000; + private const uint SingleExponentOffset = 0x3800_0000; + private const uint SingleExponent126 = 0x3F00_0000; + private const uint SingleBiasedExponentMask = 0x7F80_0000; + private const uint SingleExponent13 = 0x0680_0000; + private const uint SingleSignMask = 0x8000_0000; + private const float MaxHalfValueBelowInfinity = 65520F; + /// /// Packs a into an /// @@ -25,4 +48,304 @@ internal static class HalfTypeHelper /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static float Unpack(ushort value) => (float)BitConverter.UInt16BitsToHalf(value); + + /// + /// Normalizes a finite binary16 value to the scaled pixel range. + /// + /// The native binary16 value represented as a . + /// The normalized value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static float ToScaled(float value) => (value * InverseFiniteRange) + ScaledMidpoint; + + /// + /// Normalizes finite binary16 values to the scaled pixel range. + /// + /// The native binary16 values. + /// The normalized values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector2 ToScaled(Vector2 value) => (value * InverseFiniteRange) + new Vector2(ScaledMidpoint); + + /// + /// Normalizes finite binary16 values to the scaled pixel range. + /// + /// The native binary16 values. + /// The normalized values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 ToScaled(Vector4 value) => (value * InverseFiniteRange) + new Vector4(ScaledMidpoint); + + /// + /// Expands a normalized value to the finite binary16 range. + /// + /// The normalized value. + /// The native binary16 value represented as a . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static float FromScaled(float value) => (value * FiniteRange) + FiniteMinimum; + + /// + /// Expands normalized values to the finite binary16 range. + /// + /// The normalized values. + /// The native binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector2 FromScaled(Vector2 value) => (value * FiniteRange) + new Vector2(FiniteMinimum); + + /// + /// Expands normalized values to the finite binary16 range. + /// + /// The normalized values. + /// The native binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 FromScaled(Vector4 value) => (value * FiniteRange) + new Vector4(FiniteMinimum); + + /// + /// Unpacks eight binary16 values into two vectors of single-precision values. + /// + /// The packed binary16 values. + /// The unpacked lower and upper values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (Vector128 Lower, Vector128 Upper) Unpack(Vector128 value) + { + (Vector128 lower, Vector128 upper) = Vector128.Widen(value); + return (ConvertHalfBitsToSingle(lower), ConvertHalfBitsToSingle(upper)); + } + + /// + /// Unpacks sixteen binary16 values into two vectors of single-precision values. + /// + /// The packed binary16 values. + /// The unpacked lower and upper values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (Vector256 Lower, Vector256 Upper) Unpack(Vector256 value) + { + (Vector256 lower, Vector256 upper) = Vector256.Widen(value); + return (ConvertHalfBitsToSingle(lower), ConvertHalfBitsToSingle(upper)); + } + + /// + /// Unpacks thirty-two binary16 values into two vectors of single-precision values. + /// + /// The packed binary16 values. + /// The unpacked lower and upper values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (Vector512 Lower, Vector512 Upper) Unpack(Vector512 value) + { + (Vector512 lower, Vector512 upper) = Vector512.Widen(value); + return (ConvertHalfBitsToSingle(lower), ConvertHalfBitsToSingle(upper)); + } + + /// + /// Packs eight single-precision values into binary16 storage. + /// + /// The lower single-precision values. + /// The upper single-precision values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector128 Pack(Vector128 lower, Vector128 upper) + => Vector128.Narrow(ConvertSingleToHalfBits(lower), ConvertSingleToHalfBits(upper)); + + /// + /// Packs sixteen single-precision values into binary16 storage. + /// + /// The lower single-precision values. + /// The upper single-precision values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector256 Pack(Vector256 lower, Vector256 upper) + => Vector256.Narrow(ConvertSingleToHalfBits(lower), ConvertSingleToHalfBits(upper)); + + /// + /// Packs thirty-two single-precision values into binary16 storage. + /// + /// The lower single-precision values. + /// The upper single-precision values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector512 Pack(Vector512 lower, Vector512 upper) + => Vector512.Narrow(ConvertSingleToHalfBits(lower), ConvertSingleToHalfBits(upper)); + + /// + /// Rounds single-precision values through binary16 without changing the vector width. + /// + /// The single-precision values. + /// The values after binary16 quantization. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector128 RoundToHalf(Vector128 value) + => ConvertHalfBitsToSingle(ConvertSingleToHalfBits(value)); + + /// + /// Rounds single-precision values through binary16 without changing the vector width. + /// + /// The single-precision values. + /// The values after binary16 quantization. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector256 RoundToHalf(Vector256 value) + => ConvertHalfBitsToSingle(ConvertSingleToHalfBits(value)); + + /// + /// Rounds single-precision values through binary16 without changing the vector width. + /// + /// The single-precision values. + /// The values after binary16 quantization. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector512 RoundToHalf(Vector512 value) + => ConvertHalfBitsToSingle(ConvertSingleToHalfBits(value)); + + /// + /// Converts zero-extended binary16 bit patterns to single-precision values. + /// + /// The binary16 bit patterns. + /// The converted single-precision values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertHalfBitsToSingle(Vector128 value) + { + Vector128 sign = Vector128.ShiftLeft(value & Vector128.Create(HalfSignMask), 16); + Vector128 exponent = value & Vector128.Create(HalfExponentMask); + Vector128 subnormalMask = Vector128.Equals(exponent, Vector128.Zero); + Vector128 infinityOrNaNMask = Vector128.Equals(exponent, Vector128.Create(HalfExponentMask)); + Vector128 maskedExponentLowerBound = subnormalMask & Vector128.Create(SingleExponentLowerBound); + Vector128 exponentOffset = Vector128.Create(SingleExponentOffset) | maskedExponentLowerBound; + + // Binary16 and binary32 fraction fields differ by thirteen bits. Subnormals and special values + // need different exponent offsets before that shared field layout can be reinterpreted as float. + Vector128 bits = Vector128.ShiftLeft(value, 13) & Vector128.Create(HalfToSingleBitsMask); + exponentOffset = Vector128.ConditionalSelect(infinityOrNaNMask, Vector128.ShiftLeft(exponentOffset, 1), exponentOffset); + bits += exponentOffset; + Vector128 absoluteValue = (bits.AsSingle() - maskedExponentLowerBound.AsSingle()).AsUInt32(); + return (absoluteValue | sign).AsSingle(); + } + + /// + /// Converts zero-extended binary16 bit patterns to single-precision values. + /// + /// The binary16 bit patterns. + /// The converted single-precision values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertHalfBitsToSingle(Vector256 value) + { + Vector256 sign = Vector256.ShiftLeft(value & Vector256.Create(HalfSignMask), 16); + Vector256 exponent = value & Vector256.Create(HalfExponentMask); + Vector256 subnormalMask = Vector256.Equals(exponent, Vector256.Zero); + Vector256 infinityOrNaNMask = Vector256.Equals(exponent, Vector256.Create(HalfExponentMask)); + Vector256 maskedExponentLowerBound = subnormalMask & Vector256.Create(SingleExponentLowerBound); + Vector256 exponentOffset = Vector256.Create(SingleExponentOffset) | maskedExponentLowerBound; + + // Binary16 and binary32 fraction fields differ by thirteen bits. Subnormals and special values + // need different exponent offsets before that shared field layout can be reinterpreted as float. + Vector256 bits = Vector256.ShiftLeft(value, 13) & Vector256.Create(HalfToSingleBitsMask); + exponentOffset = Vector256.ConditionalSelect(infinityOrNaNMask, Vector256.ShiftLeft(exponentOffset, 1), exponentOffset); + bits += exponentOffset; + Vector256 absoluteValue = (bits.AsSingle() - maskedExponentLowerBound.AsSingle()).AsUInt32(); + return (absoluteValue | sign).AsSingle(); + } + + /// + /// Converts zero-extended binary16 bit patterns to single-precision values. + /// + /// The binary16 bit patterns. + /// The converted single-precision values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertHalfBitsToSingle(Vector512 value) + { + Vector512 sign = Vector512.ShiftLeft(value & Vector512.Create(HalfSignMask), 16); + Vector512 exponent = value & Vector512.Create(HalfExponentMask); + Vector512 subnormalMask = Vector512.Equals(exponent, Vector512.Zero); + Vector512 infinityOrNaNMask = Vector512.Equals(exponent, Vector512.Create(HalfExponentMask)); + Vector512 maskedExponentLowerBound = subnormalMask & Vector512.Create(SingleExponentLowerBound); + Vector512 exponentOffset = Vector512.Create(SingleExponentOffset) | maskedExponentLowerBound; + + // Binary16 and binary32 fraction fields differ by thirteen bits. Subnormals and special values + // need different exponent offsets before that shared field layout can be reinterpreted as float. + Vector512 bits = Vector512.ShiftLeft(value, 13) & Vector512.Create(HalfToSingleBitsMask); + exponentOffset = Vector512.ConditionalSelect(infinityOrNaNMask, Vector512.ShiftLeft(exponentOffset, 1), exponentOffset); + bits += exponentOffset; + Vector512 absoluteValue = (bits.AsSingle() - maskedExponentLowerBound.AsSingle()).AsUInt32(); + return (absoluteValue | sign).AsSingle(); + } + + /// + /// Converts single-precision values to zero-extended binary16 bit patterns. + /// + /// The single-precision values. + /// The binary16 bit patterns in 32-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertSingleToHalfBits(Vector128 value) + { + Vector128 bits = value.AsUInt32(); + Vector128 sign = Vector128.ShiftRightLogical(bits & Vector128.Create(SingleSignMask), 16); + Vector128 realMask = Vector128.Equals(value, value).AsUInt32(); + value = Vector128.Abs(value); + value = Vector128.Min(Vector128.Create(MaxHalfValueBelowInfinity), value); + Vector128 exponentOffset = Vector128.Max(value, Vector128.Create(SingleExponentLowerBound).AsSingle()).AsUInt32(); + exponentOffset &= Vector128.Create(SingleBiasedExponentMask); + exponentOffset += Vector128.Create(SingleExponent13); + + // Adding an exponent-sized float rounds the significand to binary16 precision using IEEE + // round-to-nearest-even. The remaining integer operations realign the exponent and sign fields. + value += exponentOffset.AsSingle(); + bits = value.AsUInt32() - Vector128.Create(SingleExponent126); + Vector128 newExponent = Vector128.ShiftRightLogical(bits, 13); + Vector128 maskedHalfExponentForNaN = ~realMask & Vector128.Create(HalfExponentMask); + bits &= realMask; + bits += newExponent; + bits &= ~maskedHalfExponentForNaN; + return bits | maskedHalfExponentForNaN | sign; + } + + /// + /// Converts single-precision values to zero-extended binary16 bit patterns. + /// + /// The single-precision values. + /// The binary16 bit patterns in 32-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertSingleToHalfBits(Vector256 value) + { + Vector256 bits = value.AsUInt32(); + Vector256 sign = Vector256.ShiftRightLogical(bits & Vector256.Create(SingleSignMask), 16); + Vector256 realMask = Vector256.Equals(value, value).AsUInt32(); + value = Vector256.Abs(value); + value = Vector256.Min(Vector256.Create(MaxHalfValueBelowInfinity), value); + Vector256 exponentOffset = Vector256.Max(value, Vector256.Create(SingleExponentLowerBound).AsSingle()).AsUInt32(); + exponentOffset &= Vector256.Create(SingleBiasedExponentMask); + exponentOffset += Vector256.Create(SingleExponent13); + + // Adding an exponent-sized float rounds the significand to binary16 precision using IEEE + // round-to-nearest-even. The remaining integer operations realign the exponent and sign fields. + value += exponentOffset.AsSingle(); + bits = value.AsUInt32() - Vector256.Create(SingleExponent126); + Vector256 newExponent = Vector256.ShiftRightLogical(bits, 13); + Vector256 maskedHalfExponentForNaN = ~realMask & Vector256.Create(HalfExponentMask); + bits &= realMask; + bits += newExponent; + bits &= ~maskedHalfExponentForNaN; + return bits | maskedHalfExponentForNaN | sign; + } + + /// + /// Converts single-precision values to zero-extended binary16 bit patterns. + /// + /// The single-precision values. + /// The binary16 bit patterns in 32-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertSingleToHalfBits(Vector512 value) + { + Vector512 bits = value.AsUInt32(); + Vector512 sign = Vector512.ShiftRightLogical(bits & Vector512.Create(SingleSignMask), 16); + Vector512 realMask = Vector512.Equals(value, value).AsUInt32(); + value = Vector512.Abs(value); + value = Vector512.Min(Vector512.Create(MaxHalfValueBelowInfinity), value); + Vector512 exponentOffset = Vector512.Max(value, Vector512.Create(SingleExponentLowerBound).AsSingle()).AsUInt32(); + exponentOffset &= Vector512.Create(SingleBiasedExponentMask); + exponentOffset += Vector512.Create(SingleExponent13); + + // Adding an exponent-sized float rounds the significand to binary16 precision using IEEE + // round-to-nearest-even. The remaining integer operations realign the exponent and sign fields. + value += exponentOffset.AsSingle(); + bits = value.AsUInt32() - Vector512.Create(SingleExponent126); + Vector512 newExponent = Vector512.ShiftRightLogical(bits, 13); + Vector512 maskedHalfExponentForNaN = ~realMask & Vector512.Create(HalfExponentMask); + bits &= realMask; + bits += newExponent; + bits &= ~maskedHalfExponentForNaN; + return bits | maskedHalfExponentForNaN | sign; + } } diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 528b3e76d4..a4f6312dd2 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -14,120 +14,150 @@ namespace SixLabors.ImageSharp.PixelFormats; public interface IPixel : IPixel, IEquatable where TSelf : unmanaged, IPixel { -#pragma warning disable CA1000 // Do not declare static members on generic types /// /// Creates a instance for this pixel type. /// This method is not intended to be consumed directly. Use instead. /// /// The instance. - static abstract PixelOperations CreatePixelOperations(); + public static abstract PixelOperations CreatePixelOperations(); /// - /// Initializes the pixel instance from a generic a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1 + /// Initializes the pixel instance from a generic ("scaled") representation using the pixel type's native alpha representation. + /// The scaled representation uses 0 and 1 as its nominal component bounds. /// /// The vector to load the pixel from. /// The . - static abstract TSelf FromScaledVector4(Vector4 source); + public static abstract TSelf FromScaledVector4(Vector4 source); /// - /// Initializes the pixel instance from a which is specific to the current pixel type. + /// Initializes the pixel instance from a generic ("scaled") whose color components use unassociated alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. /// /// The vector to load the pixel from. /// The . - static abstract TSelf FromVector4(Vector4 source); + public static abstract TSelf FromUnassociatedScaledVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a generic ("scaled") whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromAssociatedScaledVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type and uses its native alpha representation. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type and whose color components use unassociated alpha. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromUnassociatedVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type and whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromAssociatedVector4(Vector4 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromAbgr32(Abgr32 source); + public static abstract TSelf FromAbgr32(Abgr32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromArgb32(Argb32 source); + public static abstract TSelf FromArgb32(Argb32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromBgra5551(Bgra5551 source); + public static abstract TSelf FromBgra5551(Bgra5551 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromBgr24(Bgr24 source); + public static abstract TSelf FromBgr24(Bgr24 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromBgra32(Bgra32 source); + public static abstract TSelf FromBgra32(Bgra32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromL8(L8 source); + public static abstract TSelf FromL8(L8 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromL16(L16 source); + public static abstract TSelf FromL16(L16 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromLa16(La16 source); + public static abstract TSelf FromLa16(La16 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromLa32(La32 source); + public static abstract TSelf FromLa32(La32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgb24(Rgb24 source); + public static abstract TSelf FromRgb24(Rgb24 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgba32(Rgba32 source); + public static abstract TSelf FromRgba32(Rgba32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgb48(Rgb48 source); + public static abstract TSelf FromRgb48(Rgb48 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgba64(Rgba64 source); -#pragma warning restore CA1000 // Do not declare static members on generic types + public static abstract TSelf FromRgba64(Rgba64 source); } /// @@ -139,26 +169,58 @@ public interface IPixel /// Gets the pixel type information. /// /// The . - static abstract PixelTypeInfo GetPixelTypeInfo(); + public static abstract PixelTypeInfo GetPixelTypeInfo(); /// /// Convert the pixel instance into representation. /// /// The - Rgba32 ToRgba32(); + public Rgba32 ToRgba32(); /// - /// Expands the pixel into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. + /// Expands the pixel into a generic ("scaled") representation using the pixel type's native alpha representation. + /// The scaled representation uses 0 and 1 as its nominal component bounds. /// The vector components are typically expanded in least to greatest significance order. /// /// The . - Vector4 ToScaledVector4(); + public Vector4 ToScaledVector4(); + + /// + /// Expands the pixel into a generic ("scaled") whose color components use unassociated alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. + /// When alpha is zero and the pixel's native representation is associated, the color components remain unchanged + /// because no unassociated value can be recovered. + /// + /// The . + public Vector4 ToUnassociatedScaledVector4(); + + /// + /// Expands the pixel into a generic ("scaled") whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. + /// + /// The . + public Vector4 ToAssociatedScaledVector4(); /// - /// Expands the pixel into a which is specific to the current pixel type. + /// Expands the pixel into a which is specific to the current pixel type and uses its native alpha representation. /// The vector components are typically expanded in least to greatest significance order. /// /// The . - Vector4 ToVector4(); + public Vector4 ToVector4(); + + /// + /// Expands the pixel into a which is specific to the current pixel type and whose color components use unassociated alpha. + /// When alpha is zero and the pixel's native representation is associated, the color components remain unchanged + /// because no unassociated value can be recovered. + /// + /// The . + public Vector4 ToUnassociatedVector4(); + + /// + /// Expands the pixel into a which is specific to the current pixel type and whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// + /// The . + public Vector4 ToAssociatedVector4(); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs new file mode 100644 index 0000000000..94ae2ac711 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs @@ -0,0 +1,84152 @@ +// 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; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Provides generated Porter-Duff blenders for associated-alpha pixel formats. +/// +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ + + /// + /// A pixel blender that implements the "NormalSrc" composition equation. + /// + public sealed class NormalSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrc Instance { get; } = new NormalSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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.NormalSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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.NormalSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrc(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.NormalSrc(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.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrc" composition equation. + /// + public sealed class MultiplySrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrc Instance { get; } = new MultiplySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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.MultiplySrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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.MultiplySrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrc(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.MultiplySrc(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.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrc" composition equation. + /// + public sealed class AddSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrc Instance { get; } = new AddSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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.AddSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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.AddSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrc(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.AddSrc(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.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrc" composition equation. + /// + public sealed class SubtractSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrc Instance { get; } = new SubtractSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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.SubtractSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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.SubtractSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrc(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.SubtractSrc(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.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrc" composition equation. + /// + public sealed class ScreenSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrc Instance { get; } = new ScreenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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.ScreenSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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.ScreenSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrc(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.ScreenSrc(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.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrc" composition equation. + /// + public sealed class DarkenSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrc Instance { get; } = new DarkenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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.DarkenSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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.DarkenSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrc(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.DarkenSrc(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.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrc" composition equation. + /// + public sealed class LightenSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrc Instance { get; } = new LightenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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.LightenSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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.LightenSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrc(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.LightenSrc(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.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrc" composition equation. + /// + public sealed class OverlaySrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrc Instance { get; } = new OverlaySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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.OverlaySrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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.OverlaySrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrc(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.OverlaySrc(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.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrc" composition equation. + /// + public sealed class HardLightSrc : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrc Instance { get; } = new HardLightSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrc(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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.HardLightSrc(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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.HardLightSrc(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrc(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.HardLightSrc(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.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcAtop" composition equation. + /// + public sealed class NormalSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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.NormalSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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.NormalSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(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.NormalSrcAtop(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.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// + public sealed class MultiplySrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(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.MultiplySrcAtop(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.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcAtop" composition equation. + /// + public sealed class AddSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcAtop Instance { get; } = new AddSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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.AddSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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.AddSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcAtop(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.AddSrcAtop(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.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// + public sealed class SubtractSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(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.SubtractSrcAtop(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.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcAtop" composition equation. + /// + public sealed class ScreenSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(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.ScreenSrcAtop(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.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// + public sealed class DarkenSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(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.DarkenSrcAtop(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.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcAtop" composition equation. + /// + public sealed class LightenSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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.LightenSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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.LightenSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(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.LightenSrcAtop(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.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// + public sealed class OverlaySrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(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.OverlaySrcAtop(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.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcAtop" composition equation. + /// + public sealed class HardLightSrcAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(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.HardLightSrcAtop(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.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// + public sealed class NormalSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOver Instance { get; } = new NormalSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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.NormalSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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.NormalSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOver(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.NormalSrcOver(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.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// + public sealed class MultiplySrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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.MultiplySrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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.MultiplySrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(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.MultiplySrcOver(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.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOver" composition equation. + /// + public sealed class AddSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOver Instance { get; } = new AddSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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.AddSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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.AddSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOver(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.AddSrcOver(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.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// + public sealed class SubtractSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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.SubtractSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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.SubtractSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(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.SubtractSrcOver(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.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// + public sealed class ScreenSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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.ScreenSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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.ScreenSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(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.ScreenSrcOver(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.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// + public sealed class DarkenSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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.DarkenSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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.DarkenSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(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.DarkenSrcOver(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.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// + public sealed class LightenSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOver Instance { get; } = new LightenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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.LightenSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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.LightenSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOver(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.LightenSrcOver(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.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOver" composition equation. + /// + public sealed class OverlaySrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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.OverlaySrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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.OverlaySrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(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.OverlaySrcOver(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.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// + public sealed class HardLightSrcOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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.HardLightSrcOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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.HardLightSrcOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(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.HardLightSrcOver(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.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcIn" composition equation. + /// + public sealed class NormalSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcIn Instance { get; } = new NormalSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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.NormalSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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.NormalSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcIn(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.NormalSrcIn(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.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// + public sealed class MultiplySrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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.MultiplySrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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.MultiplySrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(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.MultiplySrcIn(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.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcIn" composition equation. + /// + public sealed class AddSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcIn Instance { get; } = new AddSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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.AddSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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.AddSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcIn(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.AddSrcIn(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.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// + public sealed class SubtractSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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.SubtractSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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.SubtractSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(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.SubtractSrcIn(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.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcIn" composition equation. + /// + public sealed class ScreenSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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.ScreenSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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.ScreenSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(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.ScreenSrcIn(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.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// + public sealed class DarkenSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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.DarkenSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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.DarkenSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(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.DarkenSrcIn(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.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcIn" composition equation. + /// + public sealed class LightenSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcIn Instance { get; } = new LightenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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.LightenSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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.LightenSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcIn(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.LightenSrcIn(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.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// + public sealed class OverlaySrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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.OverlaySrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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.OverlaySrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(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.OverlaySrcIn(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.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcIn" composition equation. + /// + public sealed class HardLightSrcIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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.HardLightSrcIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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.HardLightSrcIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(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.HardLightSrcIn(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.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// + public sealed class NormalSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOut Instance { get; } = new NormalSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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.NormalSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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.NormalSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalSrcOut(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.NormalSrcOut(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.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOut" composition equation. + /// + public sealed class MultiplySrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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.MultiplySrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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.MultiplySrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(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.MultiplySrcOut(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.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOut" composition equation. + /// + public sealed class AddSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOut Instance { get; } = new AddSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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.AddSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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.AddSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddSrcOut(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.AddSrcOut(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.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOut" composition equation. + /// + public sealed class SubtractSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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.SubtractSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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.SubtractSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(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.SubtractSrcOut(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.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// + public sealed class ScreenSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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.ScreenSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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.ScreenSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(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.ScreenSrcOut(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.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOut" composition equation. + /// + public sealed class DarkenSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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.DarkenSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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.DarkenSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(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.DarkenSrcOut(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.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// + public sealed class LightenSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOut Instance { get; } = new LightenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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.LightenSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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.LightenSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenSrcOut(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.LightenSrcOut(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.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOut" composition equation. + /// + public sealed class OverlaySrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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.OverlaySrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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.OverlaySrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(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.OverlaySrcOut(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.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// + public sealed class HardLightSrcOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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.HardLightSrcOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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.HardLightSrcOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(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.HardLightSrcOut(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.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDest" composition equation. + /// + public sealed class NormalDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDest Instance { get; } = new NormalDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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.NormalDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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.NormalDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDest(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.NormalDest(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.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDest" composition equation. + /// + public sealed class MultiplyDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDest Instance { get; } = new MultiplyDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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.MultiplyDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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.MultiplyDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDest(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.MultiplyDest(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.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDest" composition equation. + /// + public sealed class AddDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDest Instance { get; } = new AddDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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.AddDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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.AddDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDest(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.AddDest(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.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDest" composition equation. + /// + public sealed class SubtractDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDest Instance { get; } = new SubtractDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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.SubtractDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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.SubtractDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDest(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.SubtractDest(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.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDest" composition equation. + /// + public sealed class ScreenDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDest Instance { get; } = new ScreenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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.ScreenDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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.ScreenDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDest(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.ScreenDest(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.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDest" composition equation. + /// + public sealed class DarkenDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDest Instance { get; } = new DarkenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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.DarkenDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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.DarkenDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDest(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.DarkenDest(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.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDest" composition equation. + /// + public sealed class LightenDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDest Instance { get; } = new LightenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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.LightenDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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.LightenDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDest(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.LightenDest(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.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDest" composition equation. + /// + public sealed class OverlayDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDest Instance { get; } = new OverlayDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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.OverlayDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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.OverlayDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDest(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.OverlayDest(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.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDest" composition equation. + /// + public sealed class HardLightDest : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDest Instance { get; } = new HardLightDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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.HardLightDest(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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.HardLightDest(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDest(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.HardLightDest(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.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// + public sealed class NormalDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestAtop Instance { get; } = new NormalDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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.NormalDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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.NormalDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestAtop(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.NormalDestAtop(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.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestAtop" composition equation. + /// + public sealed class MultiplyDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(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.MultiplyDestAtop(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.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestAtop" composition equation. + /// + public sealed class AddDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestAtop Instance { get; } = new AddDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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.AddDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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.AddDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestAtop(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.AddDestAtop(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.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestAtop" composition equation. + /// + public sealed class SubtractDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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.SubtractDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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.SubtractDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(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.SubtractDestAtop(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.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// + public sealed class ScreenDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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.ScreenDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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.ScreenDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(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.ScreenDestAtop(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.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestAtop" composition equation. + /// + public sealed class DarkenDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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.DarkenDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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.DarkenDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(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.DarkenDestAtop(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.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// + public sealed class LightenDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestAtop Instance { get; } = new LightenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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.LightenDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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.LightenDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestAtop(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.LightenDestAtop(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.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestAtop" composition equation. + /// + public sealed class OverlayDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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.OverlayDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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.OverlayDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(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.OverlayDestAtop(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.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// + public sealed class HardLightDestAtop : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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.HardLightDestAtop(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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.HardLightDestAtop(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(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.HardLightDestAtop(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.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestOver" composition equation. + /// + public sealed class NormalDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOver Instance { get; } = new NormalDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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.NormalDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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.NormalDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOver(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.NormalDestOver(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.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestOver" composition equation. + /// + public sealed class MultiplyDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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.MultiplyDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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.MultiplyDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(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.MultiplyDestOver(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.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestOver" composition equation. + /// + public sealed class AddDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOver Instance { get; } = new AddDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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.AddDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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.AddDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOver(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.AddDestOver(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.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestOver" composition equation. + /// + public sealed class SubtractDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOver Instance { get; } = new SubtractDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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.SubtractDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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.SubtractDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOver(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.SubtractDestOver(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.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestOver" composition equation. + /// + public sealed class ScreenDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOver Instance { get; } = new ScreenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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.ScreenDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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.ScreenDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOver(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.ScreenDestOver(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.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestOver" composition equation. + /// + public sealed class DarkenDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOver Instance { get; } = new DarkenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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.DarkenDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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.DarkenDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOver(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.DarkenDestOver(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.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDestOver" composition equation. + /// + public sealed class LightenDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOver Instance { get; } = new LightenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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.LightenDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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.LightenDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOver(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.LightenDestOver(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.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestOver" composition equation. + /// + public sealed class OverlayDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOver Instance { get; } = new OverlayDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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.OverlayDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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.OverlayDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOver(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.OverlayDestOver(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.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestOver" composition equation. + /// + public sealed class HardLightDestOver : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOver Instance { get; } = new HardLightDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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.HardLightDestOver(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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.HardLightDestOver(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOver(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.HardLightDestOver(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.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestIn" composition equation. + /// + public sealed class NormalDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestIn Instance { get; } = new NormalDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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.NormalDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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.NormalDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestIn(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.NormalDestIn(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.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestIn" composition equation. + /// + public sealed class MultiplyDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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.MultiplyDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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.MultiplyDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(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.MultiplyDestIn(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.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestIn" composition equation. + /// + public sealed class AddDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestIn Instance { get; } = new AddDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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.AddDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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.AddDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestIn(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.AddDestIn(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.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestIn" composition equation. + /// + public sealed class SubtractDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestIn Instance { get; } = new SubtractDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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.SubtractDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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.SubtractDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestIn(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.SubtractDestIn(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.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestIn" composition equation. + /// + public sealed class ScreenDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestIn Instance { get; } = new ScreenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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.ScreenDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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.ScreenDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestIn(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.ScreenDestIn(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.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestIn" composition equation. + /// + public sealed class DarkenDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestIn Instance { get; } = new DarkenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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.DarkenDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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.DarkenDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestIn(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.DarkenDestIn(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.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDestIn" composition equation. + /// + public sealed class LightenDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestIn Instance { get; } = new LightenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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.LightenDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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.LightenDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestIn(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.LightenDestIn(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.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestIn" composition equation. + /// + public sealed class OverlayDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestIn Instance { get; } = new OverlayDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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.OverlayDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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.OverlayDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestIn(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.OverlayDestIn(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.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestIn" composition equation. + /// + public sealed class HardLightDestIn : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestIn Instance { get; } = new HardLightDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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.HardLightDestIn(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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.HardLightDestIn(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestIn(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.HardLightDestIn(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.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestOut" composition equation. + /// + public sealed class NormalDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOut Instance { get; } = new NormalDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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.NormalDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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.NormalDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalDestOut(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.NormalDestOut(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.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestOut" composition equation. + /// + public sealed class MultiplyDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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.MultiplyDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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.MultiplyDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(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.MultiplyDestOut(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.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestOut" composition equation. + /// + public sealed class AddDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOut Instance { get; } = new AddDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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.AddDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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.AddDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddDestOut(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.AddDestOut(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.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestOut" composition equation. + /// + public sealed class SubtractDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOut Instance { get; } = new SubtractDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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.SubtractDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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.SubtractDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractDestOut(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.SubtractDestOut(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.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestOut" composition equation. + /// + public sealed class ScreenDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOut Instance { get; } = new ScreenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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.ScreenDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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.ScreenDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenDestOut(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.ScreenDestOut(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.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestOut" composition equation. + /// + public sealed class DarkenDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOut Instance { get; } = new DarkenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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.DarkenDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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.DarkenDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenDestOut(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.DarkenDestOut(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.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDestOut" composition equation. + /// + public sealed class LightenDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOut Instance { get; } = new LightenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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.LightenDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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.LightenDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenDestOut(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.LightenDestOut(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.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestOut" composition equation. + /// + public sealed class OverlayDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOut Instance { get; } = new OverlayDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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.OverlayDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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.OverlayDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayDestOut(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.OverlayDestOut(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.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestOut" composition equation. + /// + public sealed class HardLightDestOut : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOut Instance { get; } = new HardLightDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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.HardLightDestOut(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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.HardLightDestOut(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightDestOut(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.HardLightDestOut(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.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalClear" composition equation. + /// + public sealed class NormalClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalClear Instance { get; } = new NormalClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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.NormalClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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.NormalClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalClear(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.NormalClear(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.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyClear" composition equation. + /// + public sealed class MultiplyClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyClear Instance { get; } = new MultiplyClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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.MultiplyClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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.MultiplyClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyClear(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.MultiplyClear(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.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddClear" composition equation. + /// + public sealed class AddClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddClear Instance { get; } = new AddClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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.AddClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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.AddClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddClear(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.AddClear(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.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractClear" composition equation. + /// + public sealed class SubtractClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractClear Instance { get; } = new SubtractClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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.SubtractClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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.SubtractClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractClear(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.SubtractClear(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.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenClear" composition equation. + /// + public sealed class ScreenClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenClear Instance { get; } = new ScreenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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.ScreenClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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.ScreenClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenClear(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.ScreenClear(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.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenClear" composition equation. + /// + public sealed class DarkenClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenClear Instance { get; } = new DarkenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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.DarkenClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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.DarkenClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenClear(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.DarkenClear(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.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenClear" composition equation. + /// + public sealed class LightenClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenClear Instance { get; } = new LightenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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.LightenClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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.LightenClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenClear(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.LightenClear(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.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayClear" composition equation. + /// + public sealed class OverlayClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayClear Instance { get; } = new OverlayClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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.OverlayClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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.OverlayClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayClear(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.OverlayClear(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.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightClear" composition equation. + /// + public sealed class HardLightClear : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightClear Instance { get; } = new HardLightClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightClear(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightClear(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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.HardLightClear(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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.HardLightClear(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightClear(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.HardLightClear(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.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalXor" composition equation. + /// + public sealed class NormalXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalXor Instance { get; } = new NormalXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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.NormalXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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.NormalXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.NormalXor(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.NormalXor(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.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyXor" composition equation. + /// + public sealed class MultiplyXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyXor Instance { get; } = new MultiplyXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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.MultiplyXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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.MultiplyXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.MultiplyXor(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.MultiplyXor(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.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddXor" composition equation. + /// + public sealed class AddXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddXor Instance { get; } = new AddXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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.AddXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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.AddXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.AddXor(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.AddXor(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.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractXor" composition equation. + /// + public sealed class SubtractXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractXor Instance { get; } = new SubtractXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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.SubtractXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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.SubtractXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.SubtractXor(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.SubtractXor(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.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenXor" composition equation. + /// + public sealed class ScreenXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenXor Instance { get; } = new ScreenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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.ScreenXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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.ScreenXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.ScreenXor(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.ScreenXor(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.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenXor" composition equation. + /// + public sealed class DarkenXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenXor Instance { get; } = new DarkenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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.DarkenXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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.DarkenXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.DarkenXor(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.DarkenXor(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.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenXor" composition equation. + /// + public sealed class LightenXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenXor Instance { get; } = new LightenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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.LightenXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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.LightenXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.LightenXor(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.LightenXor(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.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayXor" composition equation. + /// + public sealed class OverlayXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayXor Instance { get; } = new OverlayXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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.OverlayXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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.OverlayXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.OverlayXor(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.OverlayXor(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.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightXor" composition equation. + /// + public sealed class HardLightXor : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightXor Instance { get; } = new HardLightXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightXor(background[i], source[i], amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 opacity = Vector512.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightXor(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + destinationBase = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = AssociatedAlphaPorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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.HardLightXor(background[i], source[i], amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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.HardLightXor(background[i], source, amount); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = AssociatedAlphaPorterDuffFunctions.HardLightXor(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.HardLightXor(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.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt new file mode 100644 index 0000000000..69426b729f --- /dev/null +++ b/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. + +// +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; + +/// +/// Provides generated Porter-Duff blenders for associated-alpha pixel formats. +/// +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ + +<# +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}"; +#> + /// + /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// + public sealed class <#= blender_composer#> : AssociatedAlphaPixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector512 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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + Vector256 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); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector512 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 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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 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); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.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)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.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)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector512 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 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)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan 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 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 opacity = Vector512.Create(amount); + Vector512 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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 opacity = Vector256.Create(amount); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 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)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 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)); + } + } + } + + /// + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + + ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector512 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 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 containing the current four amount values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.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 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 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.Zero, coverageVector), vOne); + + Vector512 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 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + + ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + + Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); + Vector256 vOne = Vector256.Create(1F); + + while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) + { + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp them. + Vector256 opacity = Vector256.Create( + Vector128.Create(amountBase), + Vector128.Create(Unsafe.Add(ref amountBase, 1))); + opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 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)); + } + } + } + } + +<# + } +} + +#> +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs new file mode 100644 index 0000000000..6ce1235b4c --- /dev/null +++ b/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; + +/// +/// Provides pixel blenders for formats that store associated alpha. +/// +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the blender for the requested color blending and alpha composition modes. + /// + /// The color blending mode. + /// The alpha composition mode. + /// The pixel blender. + public static PixelBlender 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, + }, + }; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..9c71e97e5f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Provides the vector representation used to blend pixels that store associated alpha. +/// +/// The associated-alpha pixel format. +internal abstract class AssociatedAlphaPixelBlender : PixelBlender + where TPixel : unmanaged, IPixel +{ + private static readonly PixelOperations Operations = PixelOperations.Instance; + + /// + protected override void ToBlendVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + // Selecting the source representation once per row avoids a format check for every blended pixel. + PixelOperations.Instance.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + } + + /// + protected override Vector4 ToBlendVector4(TPixel source) => source.ToAssociatedScaledVector4(); + + /// + protected override void FromBlendVector4( + Configuration configuration, + Span source, + Span destination) + { + Operations.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs new file mode 100644 index 0000000000..cd5a37a117 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs @@ -0,0 +1,5035 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +internal static partial class AssociatedAlphaPorterDuffFunctions +{ + /// + /// Returns the associated-alpha result of the "NormalSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "NormalSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "NormalSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "MultiplySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "MultiplySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "MultiplySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "AddSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "AddSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "AddSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SubtractSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SubtractSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SubtractSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ScreenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ScreenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ScreenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DarkenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DarkenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DarkenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LightenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LightenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LightenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "OverlaySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "OverlaySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "OverlaySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HardLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HardLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HardLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "NormalSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "NormalDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "NormalDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "MultiplyDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "MultiplyDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "MultiplyDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "AddDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "AddDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "AddDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SubtractDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SubtractDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SubtractDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ScreenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ScreenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ScreenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DarkenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DarkenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DarkenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LightenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LightenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LightenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "OverlayDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "OverlayDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "OverlayDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HardLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HardLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HardLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "NormalDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "NormalDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "NormalClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "NormalClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "MultiplyClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "MultiplyClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "MultiplyClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "AddClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "AddClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "AddClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "SubtractClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "SubtractClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "SubtractClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "ScreenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "ScreenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "ScreenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "DarkenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "DarkenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "DarkenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "LightenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "LightenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "LightenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "OverlayClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "OverlayClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "OverlayClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "HardLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "HardLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "HardLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "NormalXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlayXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlayXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlayXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt new file mode 100644 index 0000000000..e0652d6799 --- /dev/null +++ b/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. + +// +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("", ".Zero"); +#> + /// + /// Returns the associated-alpha result of the "<#= function #>" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [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", + "Vector512", + }; + + 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)"; + } + } +#> diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs new file mode 100644 index 0000000000..d7b5260d36 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs @@ -0,0 +1,550 @@ +// 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; + +/// +/// Provides Porter-Duff composition functions for associated-alpha vectors. +/// +internal static partial class AssociatedAlphaPorterDuffFunctions +{ + private const int BlendAlphaControl = 0b_10_00_10_00; + private const int ShuffleAlphaControl = 0b_11_11_11_11; + + /// + /// Calculates the associated overlap term for Multiply blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Multiply(Vector4 backdrop, Vector4 source) => backdrop * source; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Multiply(Vector256 backdrop, Vector256 source) => backdrop * source; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Multiply(Vector512 backdrop, Vector512 source) => backdrop * source; + + /// + /// Calculates the associated overlap term for Add blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Add(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Add(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); + } + + /// + /// Calculates the associated overlap term for Subtract blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Subtract(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Max(Vector256.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Subtract(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Max(Vector512.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); + } + + /// + /// Calculates the associated overlap term for Screen blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Screen(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Screen(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); + } + + /// + /// Calculates the associated overlap term for Darken blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Darken(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Min(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Darken(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Min(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + /// Calculates the associated overlap term for Lighten blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Lighten(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Max(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Lighten(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Max(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + /// Calculates the associated overlap term for Overlay blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Overlay(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 left = (backdrop + backdrop) * source; + Vector256 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F)); + Vector256 useRight = Avx.CompareGreaterThan(backdrop + backdrop, backdropAlpha); + return Avx.BlendVariable(left, right, useRight); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Overlay(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 left = (backdrop + backdrop) * source; + Vector512 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F)); + Vector512 useRight = Avx512F.CompareGreaterThan(backdrop + backdrop, backdropAlpha); + return Vector512.ConditionalSelect(useRight, right, left); + } + + /// + /// Calculates the associated overlap term for HardLight blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [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); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLight(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 left = (backdrop + backdrop) * source; + Vector256 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F)); + Vector256 useRight = Avx.CompareGreaterThan(source + source, sourceAlpha); + return Avx.BlendVariable(left, right, useRight); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLight(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 left = (backdrop + backdrop) * source; + Vector512 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F)); + Vector512 useRight = Avx512F.CompareGreaterThan(source + source, sourceAlpha); + return Vector512.ConditionalSelect(useRight, right, left); + } + + /// + /// Composites an associated source over an associated destination without a color-blending function. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [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)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverNormal(Vector256 destination, Vector256 source) + { + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return source + (destination * (Vector256.Create(1F) - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverNormal(Vector512 destination, Vector512 source) + { + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return source + (destination * (Vector512.Create(1F) - sourceAlpha)); + } + + /// + /// Composites an associated source atop an associated destination without a color-blending function. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [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)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AtopNormal(Vector256 destination, Vector256 source) + { + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + return (source * destinationAlpha) + (destination * (Vector256.Create(1F) - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AtopNormal(Vector512 destination, Vector512 source) + { + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + return (source * destinationAlpha) + (destination * (Vector512.Create(1F) - sourceAlpha)); + } + + /// + /// Composites an associated source over an associated destination using an unassociated blended color. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated overlap term produced by the color-blending function. + /// The associated composition result. + [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); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Over(Vector256 destination, Vector256 source, Vector256 overlap) + { + Vector256 one = Vector256.Create(1F); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + Vector256 result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap; + Vector256 alpha = source + (destination * (one - sourceAlpha)); + return Avx.Blend(result, alpha, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Over(Vector512 destination, Vector512 source, Vector512 overlap) + { + Vector512 one = Vector512.Create(1F); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + Vector512 result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap; + Vector512 alpha = source + (destination * (one - sourceAlpha)); + return Vector512.ConditionalSelect(AlphaMask512(), alpha, result); + } + + /// + /// Composites an associated source atop an associated destination using an unassociated blended color. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated overlap term produced by the color-blending function. + /// The associated composition result. + [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 coefficient = Vector4.One - sourceAlpha; + Vector4 result = Vector128_.FusedMultiplyAdd(destination.AsVector128(), coefficient.AsVector128(), overlap.AsVector128()).AsVector4(); + + return Numerics.WithW(result, destinationAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Atop(Vector256 destination, Vector256 source, Vector256 overlap) + { + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + Vector256 result = Vector256_.FusedMultiplyAdd(destination, Vector256.Create(1F) - sourceAlpha, overlap); + + return Avx.Blend(result, destinationAlpha, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Atop(Vector512 destination, Vector512 source, Vector512 overlap) + { + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + Vector512 result = Vector512_.FusedMultiplyAdd(destination, Vector512.Create(1F) - sourceAlpha, overlap); + + return Vector512.ConditionalSelect(AlphaMask512(), destinationAlpha, result); + } + + /// + /// Retains the associated source within the destination coverage. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 In(Vector4 destination, Vector4 source) => source * Numerics.PermuteW(destination); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 In(Vector256 destination, Vector256 source) + => source * Avx.Permute(destination, ShuffleAlphaControl); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 In(Vector512 destination, Vector512 source) + => source * Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + /// + /// Retains the associated source outside the destination coverage. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Out(Vector4 destination, Vector4 source) + => source * (Vector4.One - Numerics.PermuteW(destination)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Out(Vector256 destination, Vector256 source) + => source * (Vector256.Create(1F) - Avx.Permute(destination, ShuffleAlphaControl)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Out(Vector512 destination, Vector512 source) + => source * (Vector512.Create(1F) - Vector512_.ShuffleNative(destination, ShuffleAlphaControl)); + + /// + /// Retains only the non-overlapping parts of two associated vectors. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [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)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Xor(Vector256 destination, Vector256 source) + { + Vector256 one = Vector256.Create(1F); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Xor(Vector512 destination, Vector512 source) + { + Vector512 one = Vector512.Create(1F); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha)); + } + + /// + /// Applies raster coverage to an associated composition result. + /// + /// The associated backdrop vector. + /// The associated composition result. + /// The raster coverage in the range 0 through 1. + /// The covered associated result. + [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_.FusedMultiplyAdd((source - backdrop).AsVector128(), Vector128.Create(coverage), backdrop.AsVector128()).AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 BlendWithCoverage(Vector256 backdrop, Vector256 source, Vector256 coverage) + => Vector256_.FusedMultiplyAdd(source - backdrop, coverage, backdrop); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 BlendWithCoverage(Vector512 backdrop, Vector512 source, Vector512 coverage) + => Vector512_.FusedMultiplyAdd(source - backdrop, coverage, backdrop); + + /// + /// Calculates one associated Overlay overlap component without recovering either straight component. + /// + /// The associated backdrop component. + /// The backdrop alpha. + /// The associated source component. + /// The source alpha. + /// The associated overlap component. + [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)); + } + + /// + /// Creates a SIMD lane mask selecting the alpha component of each packed vector. + /// + /// The alpha-component mask. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AlphaMask512() + => Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 05db95917e..983508d2d3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -38,7 +38,7 @@ public class NormalSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -817,7 +817,7 @@ public class MultiplySrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1596,7 +1596,7 @@ public class AddSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2375,7 +2375,7 @@ public class SubtractSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3154,7 +3154,7 @@ public class ScreenSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3933,7 +3933,7 @@ public class DarkenSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4712,7 +4712,7 @@ public class LightenSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5491,7 +5491,7 @@ public class OverlaySrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6270,7 +6270,7 @@ public class HardLightSrc : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7049,7 +7049,7 @@ public class NormalSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7828,7 +7828,7 @@ public class MultiplySrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8607,7 +8607,7 @@ public class AddSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9386,7 +9386,7 @@ public class SubtractSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10165,7 +10165,7 @@ public class ScreenSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10944,7 +10944,7 @@ public class DarkenSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -11723,7 +11723,7 @@ public class LightenSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -12502,7 +12502,7 @@ public class OverlaySrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -13281,7 +13281,7 @@ public class HardLightSrcAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -14060,7 +14060,7 @@ public class NormalSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -14839,7 +14839,7 @@ public class MultiplySrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -15618,7 +15618,7 @@ public class AddSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -16397,7 +16397,7 @@ public class SubtractSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -17176,7 +17176,7 @@ public class ScreenSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -17955,7 +17955,7 @@ public class DarkenSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -18734,7 +18734,7 @@ public class LightenSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -19513,7 +19513,7 @@ public class OverlaySrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -20292,7 +20292,7 @@ public class HardLightSrcOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -21071,7 +21071,7 @@ public class NormalSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -21850,7 +21850,7 @@ public class MultiplySrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -22629,7 +22629,7 @@ public class AddSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -23408,7 +23408,7 @@ public class SubtractSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -24187,7 +24187,7 @@ public class ScreenSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -24966,7 +24966,7 @@ public class DarkenSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -25745,7 +25745,7 @@ public class LightenSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -26524,7 +26524,7 @@ public class OverlaySrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -27303,7 +27303,7 @@ public class HardLightSrcIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -28082,7 +28082,7 @@ public class NormalSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -28861,7 +28861,7 @@ public class MultiplySrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -29640,7 +29640,7 @@ public class AddSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -30419,7 +30419,7 @@ public class SubtractSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -31198,7 +31198,7 @@ public class ScreenSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -31977,7 +31977,7 @@ public class DarkenSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -32756,7 +32756,7 @@ public class LightenSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -33535,7 +33535,7 @@ public class OverlaySrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -34314,7 +34314,7 @@ public class HardLightSrcOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -35093,7 +35093,7 @@ public class NormalDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -35872,7 +35872,7 @@ public class MultiplyDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -36651,7 +36651,7 @@ public class AddDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -37430,7 +37430,7 @@ public class SubtractDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -38209,7 +38209,7 @@ public class ScreenDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -38988,7 +38988,7 @@ public class DarkenDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -39767,7 +39767,7 @@ public class LightenDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -40546,7 +40546,7 @@ public class OverlayDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -41325,7 +41325,7 @@ public class HardLightDest : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -42104,7 +42104,7 @@ public class NormalDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -42883,7 +42883,7 @@ public class MultiplyDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -43662,7 +43662,7 @@ public class AddDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -44441,7 +44441,7 @@ public class SubtractDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -45220,7 +45220,7 @@ public class ScreenDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -45999,7 +45999,7 @@ public class DarkenDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -46778,7 +46778,7 @@ public class LightenDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -47557,7 +47557,7 @@ public class OverlayDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -48336,7 +48336,7 @@ public class HardLightDestAtop : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -49115,7 +49115,7 @@ public class NormalDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -49894,7 +49894,7 @@ public class MultiplyDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -50673,7 +50673,7 @@ public class AddDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -51452,7 +51452,7 @@ public class SubtractDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -52231,7 +52231,7 @@ public class ScreenDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -53010,7 +53010,7 @@ public class DarkenDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -53789,7 +53789,7 @@ public class LightenDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -54568,7 +54568,7 @@ public class OverlayDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -55347,7 +55347,7 @@ public class HardLightDestOver : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -56126,7 +56126,7 @@ public class NormalDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -56905,7 +56905,7 @@ public class MultiplyDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -57684,7 +57684,7 @@ public class AddDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -58463,7 +58463,7 @@ public class SubtractDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -59242,7 +59242,7 @@ public class ScreenDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -60021,7 +60021,7 @@ public class DarkenDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -60800,7 +60800,7 @@ public class LightenDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -61579,7 +61579,7 @@ public class OverlayDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -62358,7 +62358,7 @@ public class HardLightDestIn : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -63137,7 +63137,7 @@ public class NormalDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -63916,7 +63916,7 @@ public class MultiplyDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -64695,7 +64695,7 @@ public class AddDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -65474,7 +65474,7 @@ public class SubtractDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -66253,7 +66253,7 @@ public class ScreenDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -67032,7 +67032,7 @@ public class DarkenDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -67811,7 +67811,7 @@ public class LightenDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -68590,7 +68590,7 @@ public class OverlayDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -69369,7 +69369,7 @@ public class HardLightDestOut : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -70148,7 +70148,7 @@ public class NormalClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -70927,7 +70927,7 @@ public class MultiplyClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -71706,7 +71706,7 @@ public class AddClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -72485,7 +72485,7 @@ public class SubtractClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -73264,7 +73264,7 @@ public class ScreenClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -74043,7 +74043,7 @@ public class DarkenClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -74822,7 +74822,7 @@ public class LightenClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -75601,7 +75601,7 @@ public class OverlayClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -76380,7 +76380,7 @@ public class HardLightClear : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -77159,7 +77159,7 @@ public class NormalXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -77938,7 +77938,7 @@ public class MultiplyXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -78717,7 +78717,7 @@ public class AddXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.AddXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -79496,7 +79496,7 @@ public class SubtractXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -80275,7 +80275,7 @@ public class ScreenXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -81054,7 +81054,7 @@ public class DarkenXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -81833,7 +81833,7 @@ public class LightenXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -82612,7 +82612,7 @@ public class OverlayXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -83391,7 +83391,7 @@ public class HardLightXor : PixelBlender /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index e20e47f2bb..b1d8134ceb 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -81,7 +81,7 @@ var blenders = new []{ /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return TPixel.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index d32966c24e..262370cd5c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -505,7 +505,7 @@ public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -521,7 +521,7 @@ public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -537,7 +537,7 @@ public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -553,7 +553,7 @@ public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -569,7 +569,7 @@ public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -585,7 +585,7 @@ public static TPixel NormalDest(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -601,7 +601,7 @@ public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -617,7 +617,7 @@ public static TPixel NormalDestOver(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -633,7 +633,7 @@ public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -649,7 +649,7 @@ public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -665,7 +665,7 @@ public static TPixel NormalClear(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -681,7 +681,7 @@ public static TPixel NormalXor(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1176,7 +1176,7 @@ public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1192,7 +1192,7 @@ public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1208,7 +1208,7 @@ public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1224,7 +1224,7 @@ public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1240,7 +1240,7 @@ public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1256,7 +1256,7 @@ public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1272,7 +1272,7 @@ public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1288,7 +1288,7 @@ public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1304,7 +1304,7 @@ public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1320,7 +1320,7 @@ public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1336,7 +1336,7 @@ public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1352,7 +1352,7 @@ public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1847,7 +1847,7 @@ public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacit where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1863,7 +1863,7 @@ public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1879,7 +1879,7 @@ public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1895,7 +1895,7 @@ public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opac where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1911,7 +1911,7 @@ public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1927,7 +1927,7 @@ public static TPixel AddDest(TPixel backdrop, TPixel source, float opaci where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1943,7 +1943,7 @@ public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1959,7 +1959,7 @@ public static TPixel AddDestOver(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1975,7 +1975,7 @@ public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1991,7 +1991,7 @@ public static TPixel AddDestOut(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2007,7 +2007,7 @@ public static TPixel AddClear(TPixel backdrop, TPixel source, float opac where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2023,7 +2023,7 @@ public static TPixel AddXor(TPixel backdrop, TPixel source, float opacit where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2518,7 +2518,7 @@ public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2534,7 +2534,7 @@ public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2550,7 +2550,7 @@ public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2566,7 +2566,7 @@ public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2582,7 +2582,7 @@ public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2598,7 +2598,7 @@ public static TPixel SubtractDest(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2614,7 +2614,7 @@ public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2630,7 +2630,7 @@ public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2646,7 +2646,7 @@ public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2662,7 +2662,7 @@ public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2678,7 +2678,7 @@ public static TPixel SubtractClear(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2694,7 +2694,7 @@ public static TPixel SubtractXor(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3189,7 +3189,7 @@ public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3205,7 +3205,7 @@ public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3221,7 +3221,7 @@ public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3237,7 +3237,7 @@ public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3253,7 +3253,7 @@ public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3269,7 +3269,7 @@ public static TPixel ScreenDest(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3285,7 +3285,7 @@ public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3301,7 +3301,7 @@ public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3317,7 +3317,7 @@ public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3333,7 +3333,7 @@ public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3349,7 +3349,7 @@ public static TPixel ScreenClear(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3365,7 +3365,7 @@ public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3860,7 +3860,7 @@ public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3876,7 +3876,7 @@ public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3892,7 +3892,7 @@ public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3908,7 +3908,7 @@ public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3924,7 +3924,7 @@ public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3940,7 +3940,7 @@ public static TPixel DarkenDest(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3956,7 +3956,7 @@ public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3972,7 +3972,7 @@ public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3988,7 +3988,7 @@ public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4004,7 +4004,7 @@ public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4020,7 +4020,7 @@ public static TPixel DarkenClear(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4036,7 +4036,7 @@ public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4531,7 +4531,7 @@ public static TPixel LightenSrc(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4547,7 +4547,7 @@ public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4563,7 +4563,7 @@ public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4579,7 +4579,7 @@ public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4595,7 +4595,7 @@ public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4611,7 +4611,7 @@ public static TPixel LightenDest(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4627,7 +4627,7 @@ public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4643,7 +4643,7 @@ public static TPixel LightenDestOver(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4659,7 +4659,7 @@ public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4675,7 +4675,7 @@ public static TPixel LightenDestOut(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4691,7 +4691,7 @@ public static TPixel LightenClear(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4707,7 +4707,7 @@ public static TPixel LightenXor(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5202,7 +5202,7 @@ public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5218,7 +5218,7 @@ public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5234,7 +5234,7 @@ public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5250,7 +5250,7 @@ public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5266,7 +5266,7 @@ public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5282,7 +5282,7 @@ public static TPixel OverlayDest(TPixel backdrop, TPixel source, float o where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5298,7 +5298,7 @@ public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5314,7 +5314,7 @@ public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5330,7 +5330,7 @@ public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5346,7 +5346,7 @@ public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5362,7 +5362,7 @@ public static TPixel OverlayClear(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5378,7 +5378,7 @@ public static TPixel OverlayXor(TPixel backdrop, TPixel source, float op where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5873,7 +5873,7 @@ public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5889,7 +5889,7 @@ public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5905,7 +5905,7 @@ public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5921,7 +5921,7 @@ public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5937,7 +5937,7 @@ public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5953,7 +5953,7 @@ public static TPixel HardLightDest(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5969,7 +5969,7 @@ public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, f where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -5985,7 +5985,7 @@ public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, f where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -6001,7 +6001,7 @@ public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, flo where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -6017,7 +6017,7 @@ public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, fl where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -6033,7 +6033,7 @@ public static TPixel HardLightClear(TPixel backdrop, TPixel source, floa where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -6049,6 +6049,6 @@ public static TPixel HardLightXor(TPixel backdrop, TPixel source, float where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 7cb007bcae..8837a90a3f 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -518,7 +518,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(<#=blender#><#=composer#>(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } <# } #> <# diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index b764432e8a..46d97ad3dc 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -338,7 +338,9 @@ public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float 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_.MultiplyAddEstimate((sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage), backdropPremultiplied.AsVector128()).AsVector4(); Numerics.UnPremultiply(ref result); return result; @@ -358,7 +360,7 @@ public static Vector256 BlendWithCoverage(Vector256 backdrop, Vect Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); Vector256 backdropPremultiplied = Avx.Blend(backdrop * backdropAlpha, backdropAlpha, BlendAlphaControl); Vector256 sourcePremultiplied = Avx.Blend(source * sourceAlpha, sourceAlpha, BlendAlphaControl); - Vector256 result = Vector256_.MultiplyAdd(backdropPremultiplied, sourcePremultiplied - backdropPremultiplied, coverage); + Vector256 result = Vector256_.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied); return Numerics.UnPremultiply(result, Avx.Permute(result, ShuffleAlphaControl)); } @@ -378,7 +380,7 @@ public static Vector512 BlendWithCoverage(Vector512 backdrop, Vect Vector512 alphaMask = AlphaMask512(); Vector512 backdropPremultiplied = Vector512.ConditionalSelect(alphaMask, backdropAlpha, backdrop * backdropAlpha); Vector512 sourcePremultiplied = Vector512.ConditionalSelect(alphaMask, sourceAlpha, source * sourceAlpha); - Vector512 result = Vector512_.MultiplyAdd(backdropPremultiplied, sourcePremultiplied - backdropPremultiplied, coverage); + Vector512 result = Vector512_.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied); return Numerics.UnPremultiply(result, Vector512_.ShuffleNative(result, ShuffleAlphaControl)); } @@ -481,8 +483,8 @@ public static Vector256 Over(Vector256 destination, Vector256 color = destination * dstW; - color = Vector256_.MultiplyAdd(color, source, srcW); - color = Vector256_.MultiplyAdd(color, blend, blendW); + color = Vector256_.MultiplyAddEstimate(source, srcW, color); + color = Vector256_.MultiplyAddEstimate(blend, blendW, color); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -511,8 +513,8 @@ public static Vector512 Over(Vector512 destination, Vector512 color = destination * dstW; - color = Vector512_.MultiplyAdd(color, source, srcW); - color = Vector512_.MultiplyAdd(color, blend, blendW); + color = Vector512_.MultiplyAddEstimate(source, srcW, color); + color = Vector512_.MultiplyAddEstimate(blend, blendW, color); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -565,7 +567,7 @@ public static Vector256 Atop(Vector256 destination, Vector256 dstW = alpha - blendW; // calculate final color - Vector256 color = Vector256_.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW); + Vector256 color = Vector256_.MultiplyAddEstimate(destination, dstW, Avx.Multiply(blend, blendW)); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -590,7 +592,7 @@ public static Vector512 Atop(Vector512 destination, Vector512 dstW = alpha - blendW; // calculate final color - Vector512 color = Vector512_.MultiplyAdd(blend * blendW, destination, dstW); + Vector512 color = Vector512_.MultiplyAddEstimate(destination, dstW, blend * blendW); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -749,8 +751,8 @@ public static Vector256 Xor(Vector256 destination, Vector256 dstW = vOne - sW; // calculate alpha - Vector256 alpha = Vector256_.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW); - Vector256 color = Vector256_.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW); + Vector256 alpha = Vector256_.MultiplyAddEstimate(sW, srcW, Avx.Multiply(dW, dstW)); + Vector256 color = Vector256_.MultiplyAddEstimate(Avx.Multiply(sW, source), srcW, Avx.Multiply(Avx.Multiply(dW, destination), dstW)); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -774,8 +776,8 @@ public static Vector512 Xor(Vector512 destination, Vector512 dstW = vOne - sW; // calculate alpha - Vector512 alpha = Vector512_.MultiplyAdd(dW * dstW, sW, srcW); - Vector512 color = Vector512_.MultiplyAdd((dW * destination) * dstW, sW * source, srcW); + Vector512 alpha = Vector512_.MultiplyAddEstimate(sW, srcW, dW * dstW); + Vector512 color = Vector512_.MultiplyAddEstimate(sW * source, srcW, (dW * destination) * dstW); // unpremultiply return Numerics.UnPremultiply(color, alpha); diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 0b1a51d74f..bb519c1741 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -93,12 +93,12 @@ public void Blend( Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); Span sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength); - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -161,11 +161,11 @@ public void Blend( Span destinationVectors = workingBuffer[..maxLength]; Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -242,12 +242,12 @@ public void BlendWithCoverage( Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); Span sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength); - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -317,11 +317,11 @@ public void BlendWithCoverage( Span destinationVectors = workingBuffer[..maxLength]; Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -463,12 +463,12 @@ public void Blend( Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); Span sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength); - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -499,11 +499,11 @@ public void Blend( Span destinationVectors = workingBuffer[..maxLength]; Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -660,12 +660,12 @@ public void BlendWithCoverage( Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); Span sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength); - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -699,13 +699,47 @@ public void BlendWithCoverage( Span destinationVectors = workingBuffer[..maxLength]; Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); - PixelOperations.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.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } + /// + /// Converts source pixels to the scaled-vector representation consumed by this blender. + /// + /// The source pixel format. + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToBlendVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TPixelSource : unmanaged, IPixel + => PixelOperations.Instance.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + /// + /// Converts a source pixel to the vector representation consumed by the blend functions. + /// + /// The source pixel. + /// The source vector. + protected virtual Vector4 ToBlendVector4(TPixel source) + => source.ToUnassociatedScaledVector4(); + + /// + /// Converts blend results from this blender's scaled-vector representation to destination pixels. + /// + /// The configuration. + /// The source vectors. + /// The destination pixels. + protected virtual void FromBlendVector4( + Configuration configuration, + Span source, + Span destination) + => PixelOperations.Instance.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + /// /// Blend 2 rows together. /// diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs index edc04fa7ce..8cee6057da 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -6,32 +6,38 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Flags responsible to select additional operations which could be efficiently applied in +/// Specifies vector representation transformations applied by /// -/// or +/// and /// -/// knowing the pixel type. +/// during bulk pixel conversion. /// [Flags] public enum PixelConversionModifiers { /// - /// No special operation is selected + /// Preserves the pixel format's native numeric range and alpha representation. /// None = 0, /// - /// Select and instead the standard (non scaled) variants. + /// Requests the scaled numeric range represented by and . /// Scale = 1 << 0, /// - /// Enable alpha premultiplication / unpremultiplication + /// Requests associated alpha for the vector representation. + /// When combined with , associated alpha takes precedence. /// Premultiply = 1 << 1, /// - /// Enable SRGB companding (defined in ). + /// Expands sRGB color components when converting pixels to vectors and compresses linear RGB color components when converting vectors to pixels. /// SRgbCompand = 1 << 2, + + /// + /// Requests unassociated alpha for the vector representation unless is also specified. + /// + UnPremultiply = 1 << 3, } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index 94fbf7eb76..8a17831a1c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -8,10 +8,11 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// Packed pixel type containing a single 8-bit normalized alpha value. -/// -/// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. -/// /// +/// +/// and scaled vector conversions return alpha in [0, 1] with zero color components. +/// The storage layout matches DXGI_FORMAT_A8_UNORM. +/// public partial struct A8 : IPixel, IPackedVector { /// @@ -77,6 +78,38 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromAssociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromAssociatedVector4(Vector4 source) => FromVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static A8 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs index 1190d307a7..acd354b7f2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -182,7 +182,7 @@ public uint PackedValue /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -194,6 +194,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Abgr32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs new file mode 100644 index 0000000000..9105a99f7f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs @@ -0,0 +1,273 @@ +// 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; + +/// +/// 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. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Abgr32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Abgr32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Abgr32P(byte r, byte g, byte b, byte a) + { + this.A = a; + this.B = b; + this.G = g; + this.R = r; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Abgr32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Abgr32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Abgr32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Abgr32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Abgr32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Abgr32P left, Abgr32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Abgr32P left, Abgr32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.Alpha | PixelColorType.BGR, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Abgr32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToAbgr32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToAbgr32P(source); + } + + /// + public static Abgr32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Abgr32P other && this.Equals(other); + + /// + public readonly bool Equals(Abgr32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Abgr32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Abgr32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Abgr32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index b74f5db718..90ae882631 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -175,7 +175,7 @@ public uint PackedValue /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -187,6 +187,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Argb32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs new file mode 100644 index 0000000000..7c866c8fe3 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs @@ -0,0 +1,273 @@ +// 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; + +/// +/// 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. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Argb32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Argb32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Argb32P(byte r, byte g, byte b, byte a) + { + this.A = a; + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Argb32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Argb32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Argb32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Argb32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Argb32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Argb32P left, Argb32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Argb32P left, Argb32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.Alpha | PixelColorType.RGB, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Argb32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToArgb32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToArgb32P(source); + } + + /// + public static Argb32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Argb32P other && this.Equals(other); + + /// + public readonly bool Equals(Argb32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Argb32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Argb32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Argb32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 944cdf7bf5..c64047518e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -97,6 +97,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgr24 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 87055bf22d..338c55fe36 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -7,14 +7,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// Packed pixel type containing three unsigned normalized values. /// The x and z components use 5 bits, and the y component uses 6 bits. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// /// /// -/// Initializes a new instance of the struct. +/// and scaled vector conversions return color components in [0, 1] with an implicit +/// alpha of 1. The storage layout matches DXGI_FORMAT_B5G6R5_UNORM. /// /// /// The vector containing the components for the packed value. @@ -75,6 +73,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index 903d9dc8cb..e8405dd2b9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -124,7 +124,7 @@ public uint PackedValue /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -136,6 +136,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgra32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs new file mode 100644 index 0000000000..6ce3138f7c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs @@ -0,0 +1,273 @@ +// 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; + +/// +/// 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. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Bgra32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Bgra32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Bgra32P(byte r, byte g, byte b, byte a) + { + this.B = b; + this.G = g; + this.R = r; + this.A = a; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Bgra32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Bgra32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Bgra32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Bgra32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Bgra32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Bgra32P left, Bgra32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Bgra32P left, Bgra32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.BGR | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Bgra32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToBgra32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToBgra32P(source); + } + + /// + public static Bgra32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Bgra32P other && this.Equals(other); + + /// + public readonly bool Equals(Bgra32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Bgra32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Bgra32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Bgra32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 55971210c3..17711835a1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -7,11 +7,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four unsigned normalized values using 4 bits per component. /// +/// +/// and scaled vector conversions return all components in [0, 1]. The storage layout matches +/// DXGI_FORMAT_B4G4R4A4_UNORM. +/// public partial struct Bgra4444 : IPixel, IPackedVector { /// @@ -88,6 +89,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgra4444 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index 4c94dea5f1..22a2c1f99f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -7,12 +7,13 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. -/// The x , y and z components use 5 bits, and the w component uses 1 bit. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four unsigned normalized values. +/// The x, y, and z components use 5 bits, and the w component uses 1 bit. /// +/// +/// and scaled vector conversions return all components in [0, 1]. The storage layout matches +/// DXGI_FORMAT_B5G5R5A1_UNORM. +/// public partial struct Bgra5551 : IPixel, IPackedVector { /// @@ -86,6 +87,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgra5551 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 680a7ee0bd..c0372ab979 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -8,11 +8,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. -/// -/// Ranges from [0, 0, 0, 0] to [255, 255, 255, 255] in vector form. -/// +/// Packed pixel type containing four 8-bit unsigned integer values. /// +/// +/// returns components in [0, 255]. Scaled vector conversions map that range to +/// [0, 1]. The storage layout matches DXGI_FORMAT_R8G8B8A8_UINT. +/// public partial struct Byte4 : IPixel, IPackedVector { private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); @@ -88,6 +89,58 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use [0, 255], so association occurs in scaled [0, 1] space before mapping the result back. + return vector * MaxBytes; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromAssociatedVector4(Vector4 source) + { + // Map every native component, including alpha, to scaled [0, 1] space before unassociating. + source /= MaxBytes; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Byte4 FromScaledVector4(Vector4 source) => FromVector4(source * 255f); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index 00deadb128..cbb0cf0866 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -7,11 +7,13 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing a single 16 bit floating point value. -/// -/// Ranges from [-1, 0, 0, 1] to [1, 0, 0, 1] in vector form. -/// +/// Packed pixel type containing a single IEEE 754 binary16 floating-point value. /// +/// +/// returns the stored IEEE 754 binary16 value directly. Scaled vector conversions normalize +/// the finite range [-65504, 65504] to [0, 1]. The packed representation is binary-compatible with +/// DXGI_FORMAT_R16_FLOAT. +/// public partial struct HalfSingle : IPixel, IPackedVector { /// @@ -53,9 +55,8 @@ public partial struct HalfSingle : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - float single = this.ToSingle() + 1F; - single /= 2F; - return new Vector4(single, 0, 0, 1F); + float scaled = HalfTypeHelper.ToScaled(this.ToSingle()); + return new Vector4(scaled, 0, 0, 1F); } /// @@ -72,16 +73,53 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromScaledVector4(Vector4 source) + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromAssociatedScaledVector4(Vector4 source) { - float scaled = source.X; - scaled *= 2F; - scaled--; - return new HalfSingle { PackedValue = HalfTypeHelper.Pack(scaled) }; + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromAssociatedVector4(Vector4 source) + { + // This format has no native alpha component: X uses the binary16 finite range, while W remains the source opacity. + source.X = HalfTypeHelper.ToScaled(source.X); + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromScaledVector4(Vector4 source) + => new() { PackedValue = HalfTypeHelper.Pack(HalfTypeHelper.FromScaled(source.X)) }; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfSingle FromVector4(Vector4 source) => new() { PackedValue = HalfTypeHelper.Pack(source.X) }; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 03d4dee892..ad6c7aefab 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -7,11 +7,13 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 16-bit floating-point values. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two IEEE 754 binary16 floating-point values. /// +/// +/// and return the stored IEEE 754 binary16 values directly. Scaled +/// vector conversions normalize the finite range [-65504, 65504] to [0, 1]. The packed representation is +/// binary-compatible with DXGI_FORMAT_R16G16_FLOAT. +/// public partial struct HalfVector2 : IPixel, IPackedVector { /// @@ -60,9 +62,7 @@ public partial struct HalfVector2 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2F; + Vector2 scaled = HalfTypeHelper.ToScaled(this.ToVector2()); return new Vector4(scaled, 0F, 1F); } @@ -84,13 +84,56 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromAssociatedVector4(Vector4 source) + { + // This format has no native alpha component: XY use the binary16 finite range, while W remains the source opacity. + Vector2 scaled = HalfTypeHelper.ToScaled(new Vector2(source.X, source.Y)); + source.X = scaled.X; + source.Y = scaled.Y; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector2 FromScaledVector4(Vector4 source) { - Vector2 scaled = new Vector2(source.X, source.Y) * 2F; - scaled -= Vector2.One; - return new HalfVector2 { PackedValue = Pack(scaled.X, scaled.Y) }; + Vector2 native = HalfTypeHelper.FromScaled(new Vector2(source.X, source.Y)); + return new HalfVector2 { PackedValue = Pack(native.X, native.Y) }; } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index d0b57d788f..42531937cb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -7,11 +7,13 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 16-bit floating-point values. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four IEEE 754 binary16 floating-point values. /// +/// +/// returns the stored IEEE 754 binary16 values directly. Scaled vector conversions normalize +/// the finite range [-65504, 65504] to [0, 1]. The packed representation is binary-compatible with +/// DXGI_FORMAT_R16G16B16A16_FLOAT. +/// public partial struct HalfVector4 : IPixel, IPackedVector { /// @@ -63,13 +65,7 @@ public HalfVector4(float x, float y, float z, float w) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; - } + public readonly Vector4 ToScaledVector4() => HalfTypeHelper.ToScaled(this.ToVector4()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -89,15 +85,62 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); - /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromScaledVector4(Vector4 source) + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() { - source *= 2f; - source -= Vector4.One; - return FromVector4(source); + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Association is defined in scaled color space, so map the associated result back to the native binary16 range. + return HalfTypeHelper.FromScaled(vector); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromAssociatedVector4(Vector4 source) + { + // Restore scaled opacity before unassociating the color channels. + source = HalfTypeHelper.ToScaled(source); + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromScaledVector4(Vector4 source) => FromVector4(HalfTypeHelper.FromScaled(source)); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector4 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs new file mode 100644 index 0000000000..50fc272553 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 16-bit floating-point values. +/// +/// +/// returns the stored associated IEEE 754 binary16 values directly. Scaled vector conversions +/// normalize the finite range [-65504, 65504] to [0, 1] while preserving associated alpha. The packed +/// representation is binary-compatible with DXGI_FORMAT_R16G16B16A16_FLOAT. +/// +public partial struct HalfVector4P : IPixel, IPackedVector +{ + /// + /// Initializes a new instance of the struct. + /// + /// The associated x-component. + /// The associated y-component. + /// The associated z-component. + /// The alpha component. + public HalfVector4P(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the associated component values. + public HalfVector4P(Vector4 vector) => this.PackedValue = Pack(vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(HalfVector4P left, HalfVector4P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(HalfVector4P left, HalfVector4P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + { + return Rgba32.FromScaledVector4(this.ToUnassociatedScaledVector4()); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => HalfTypeHelper.ToScaled(this.ToVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + 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))); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + Vector4 vector = this.ToUnassociatedScaledVector4(); + return HalfTypeHelper.FromScaled(vector); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16, 16), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + public static HalfVector4P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static HalfVector4P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromUnassociatedVector4(Vector4 source) + { + source = HalfTypeHelper.ToScaled(source); + return FromUnassociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromAssociatedVector4(Vector4 source) + { + source = HalfTypeHelper.ToScaled(source); + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromUnassociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Associate(source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromAssociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Reassociate(source)); + + /// + public static HalfVector4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is HalfVector4P other && this.Equals(other); + + /// + public readonly bool Equals(HalfVector4P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + 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.##})"); + } + + /// + /// Converts an unassociated scaled vector to the associated representation of a half-precision destination. + /// + /// The unassociated scaled vector. + /// The associated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Associate(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + + // RGB must use the scaled alpha that the binary16 representation can reproduce. + source.W = QuantizeScaledAlpha(source.W); + Numerics.Premultiply(ref source); + return source; + } + + /// + /// Reassociates a scaled vector with the alpha value the destination stores. + /// + /// The associated scaled vector. + /// The reassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Reassociate(Vector4 source) + { + float alpha = source.W; + + if (alpha <= 0) + { + return Vector4.Zero; + } + + float storedAlpha = QuantizeScaledAlpha(alpha); + + // 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; + Numerics.ClampRgbToAlpha(ref source); + return source; + } + + /// + /// Packs an associated scaled vector into the native binary16 representation. + /// + /// The associated scaled vector. + /// The packed pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static HalfVector4P PackAssociatedScaledVector4(Vector4 source) + { + source = HalfTypeHelper.FromScaled(source); + return new HalfVector4P { PackedValue = Pack(source) }; + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha value. + /// The scaled value represented by the stored binary16 component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float QuantizeScaledAlpha(float alpha) + { + float nativeAlpha = HalfTypeHelper.FromScaled(Numerics.Clamp(alpha, 0F, 1F)); + return HalfTypeHelper.ToScaled(HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha))); + } + + /// + /// Packs native half-precision components into a 64-bit value. + /// + /// The native component values. + /// The packed value. + 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; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index c5893f7706..1ab8e9650b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -77,6 +77,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static L16 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs index 008eed459d..9908e03067 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -79,6 +79,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static L8 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs index 6c3fa78295..3a15f24d51 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -106,6 +106,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static La16 FromScaledVector4(Vector4 source) => Pack(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs index c99f6fe603..ae3190d964 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -103,6 +103,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static La32 FromScaledVector4(Vector4 source) => Pack(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index da2ab2440c..6039bd9d7c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -7,15 +7,20 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two 8-bit signed normalized values. /// +/// +/// and return components in the native signed-normalized range +/// [-1, 1]. Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -128 and -127 both represent -1, +/// matching DXGI_FORMAT_R8G8_SNORM. +/// public partial struct NormalizedByte2 : IPixel, IPackedVector { private const float MaxPos = 127f; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector2 Half = new(MaxPos); + private static readonly Vector2 Minimum = new(-MaxPos); private static readonly Vector2 MinusOne = new(-1f); /// @@ -67,9 +72,14 @@ public NormalizedByte2(float x, float y) [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2f; + Vector2 scaled = new( + (sbyte)(this.PackedValue & 0xFF), + (sbyte)(this.PackedValue >> 8)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -128 cannot escape the scaled range. + scaled = Vector2.Max(scaled, Minimum); + scaled += Half; + scaled /= ScaledMagnitude; return new Vector4(scaled, 0f, 1f); } @@ -87,6 +97,49 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromAssociatedVector4(Vector4 source) + { + // Only the stored color components use the native [-1, 1] encoding; W remains the normalized source alpha. + source.X = (source.X + 1F) / 2F; + source.Y = (source.Y + 1F) / 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedByte2 FromScaledVector4(Vector4 source) @@ -158,9 +211,15 @@ public static NormalizedByte2 FromScaledVector4(Vector4 source) /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos); + public readonly Vector2 ToVector2() + { + Vector2 vector = new( + (sbyte)(this.PackedValue & 0xFF), + (sbyte)(this.PackedValue >> 8)); + + // DirectX SNORM maps both -128 and -127 to -1. + return Vector2.Max(vector, Minimum) / MaxPos; + } /// public override readonly bool Equals(object? obj) => obj is NormalizedByte2 other && this.Equals(other); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 1fb386725a..2f91718cf8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -8,15 +8,20 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four 8-bit signed normalized values. /// +/// +/// returns components in the native signed-normalized range [-1, 1]. +/// Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -128 and -127 both represent -1, +/// matching DXGI_FORMAT_R8G8B8A8_SNORM. +/// public partial struct NormalizedByte4 : IPixel, IPackedVector { private const float MaxPos = 127f; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4(); + private static readonly Vector4 Minimum = -Half; private static readonly Vector4 MinusOne = Vector128.Create(-1f).AsVector4(); /// @@ -70,19 +75,30 @@ public NormalizedByte4(float x, float y, float z, float w) [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; + // Offset the exact signed components before division. Mapping an already normalized value through (value + 1) / 2 loses precision near -1 through cancellation. + Vector4 scaled = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -128 cannot escape the scaled range. + return (Vector4.Max(scaled, Minimum) + Half) / ScaledMagnitude; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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); + public readonly Vector4 ToVector4() + { + Vector4 vector = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // DirectX SNORM maps both -128 and -127 to -1. + return Vector4.Max(vector, Minimum) / MaxPos; + } /// public static PixelTypeInfo GetPixelTypeInfo() @@ -94,6 +110,61 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use an affine [-1, 1] encoding, so direct multiplication would use the wrong zero point. + vector *= 2F; + vector -= Vector4.One; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromAssociatedVector4(Vector4 source) + { + // Map the affine native encoding to logical [0, 1] space before unassociating. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedByte4 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs new file mode 100644 index 0000000000..1172aafb95 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs @@ -0,0 +1,318 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1. +/// +/// +/// returns associated components in the native signed-normalized range [-1, 1]. +/// Scaled vector conversions return associated components in [0, 1]. +/// The packed two's-complement codes -128 and -127 both represent -1, +/// matching DXGI_FORMAT_R8G8B8A8_SNORM. +/// +public partial struct NormalizedByte4P : IPixel, IPackedVector +{ + private const float MaxPos = 127F; + private const float ScaledMagnitude = MaxPos * 2F; + private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4(); + private static readonly Vector4 Minimum = -Half; + private static readonly Vector4 MinusOne = Vector128.Create(-1F).AsVector4(); + + /// + /// Initializes a new instance of the struct. + /// + /// The associated x-component. + /// The associated y-component. + /// The associated z-component. + /// The alpha component. + public NormalizedByte4P(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the associated component values. + public NormalizedByte4P(Vector4 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(NormalizedByte4P left, NormalizedByte4P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(NormalizedByte4P left, NormalizedByte4P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(this)); + + /// + public readonly Vector4 ToScaledVector4() + { + // Offset the exact signed components before division. Mapping an already normalized value through (value + 1) / 2 loses precision near -1 through cancellation. + Vector4 scaled = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -128 cannot escape the scaled range. + return (Vector4.Max(scaled, Minimum) + Half) / ScaledMagnitude; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => ToUnassociatedScaledVector4(this); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() + { + Vector4 vector = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // DirectX SNORM maps both -128 and -127 to -1. + return Vector4.Max(vector, Minimum) / MaxPos; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + // Association is defined in the common scaled domain. Signed-native W is an affine encoding of alpha, so it cannot be used as a divisor directly. + Vector4 vector = this.ToUnassociatedScaledVector4(); + vector *= 2F; + vector -= Vector4.One; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + public static NormalizedByte4P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static NormalizedByte4P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromUnassociatedVector4(Vector4 source) + { + // Convert the signed-native range to the common scaled domain before associating because native W is not opacity. + source += Vector4.One; + source /= 2F; + return FromUnassociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromAssociatedVector4(Vector4 source) + { + // Reassociation must also operate in scaled space so RGB follows the quantized scaled alpha rather than the signed-native W component. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Associate(source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromAssociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Reassociate(source)); + + /// + /// Packs an associated scaled vector into signed-normalized storage. + /// + /// The associated scaled vector. + /// The packed pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static NormalizedByte4P PackAssociatedScaledVector4(Vector4 source) + { + // Signed-normalized storage uses the native [-1, 1] range even though association is defined in scaled opacity space. + source *= 2F; + source -= Vector4.One; + return new() { PackedValue = Pack(source) }; + } + + /// + public static NormalizedByte4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is NormalizedByte4P other && this.Equals(other); + + /// + public readonly bool Equals(NormalizedByte4P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + 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.##})"); + } + + /// + /// Converts an unassociated scaled vector to the associated representation of a signed-normalized-byte destination. + /// + /// The unassociated scaled vector. + /// The associated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Associate(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + + // 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; + } + + /// + /// Converts the stored associated components to an unassociated scaled vector. + /// + /// The associated pixel. + /// The unassociated scaled vector. + [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), + (sbyte)(source.PackedValue >> 8), + (sbyte)(source.PackedValue >> 16), + (sbyte)(source.PackedValue >> 24)); + + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + vector = Vector4.Max(vector, Minimum) + Half; + + 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; + } + + /// + /// Reassociates a scaled vector with the alpha value the destination stores. + /// + /// The associated scaled vector. + /// The reassociated scaled vector. + [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; + Numerics.ClampRgbToAlpha(ref source); + return source; + } + + /// + /// Packs native signed normalized components into a 32-bit value. + /// + /// The native component values. + /// The packed value. + 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; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 0942c3a767..3da0f7f0b9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -7,15 +7,19 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two 16-bit signed normalized values. /// +/// +/// and return components in the native signed-normalized range +/// [-1, 1]. Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -32768 and -32767 both represent -1, +/// matching DXGI_FORMAT_R16G16_SNORM. +/// public partial struct NormalizedShort2 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; private const float MaxPos = 0x7FFF; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector2 Max = new(MaxPos); private static readonly Vector2 Min = Vector2.Negate(Max); @@ -69,9 +73,14 @@ public NormalizedShort2(float x, float y) [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2f; + Vector2 scaled = new( + (short)(this.PackedValue & 0xFFFF), + (short)(this.PackedValue >> 0x10)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -32768 cannot escape the scaled range. + scaled = Vector2.Max(scaled, Min); + scaled += Max; + scaled /= ScaledMagnitude; return new Vector4(scaled, 0f, 1f); } @@ -89,6 +98,49 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromAssociatedVector4(Vector4 source) + { + // Only the stored color components use the native [-1, 1] encoding; W remains the normalized source alpha. + source.X = (source.X + 1F) / 2F; + source.Y = (source.Y + 1F) / 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedShort2 FromScaledVector4(Vector4 source) @@ -160,9 +212,15 @@ public static NormalizedShort2 FromScaledVector4(Vector4 source) /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new( - (short)(this.PackedValue & 0xFFFF) / MaxPos, - (short)(this.PackedValue >> 0x10) / MaxPos); + public readonly Vector2 ToVector2() + { + Vector2 vector = new( + (short)(this.PackedValue & 0xFFFF), + (short)(this.PackedValue >> 0x10)); + + // DirectX SNORM maps both -32768 and -32767 to -1. + return Vector2.Max(vector, Min) / MaxPos; + } /// public override readonly bool Equals(object? obj) => obj is NormalizedShort2 other && this.Equals(other); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index 2b33fec27a..7acac0b7dd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -8,15 +8,19 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four 16-bit signed normalized values. /// +/// +/// returns components in the native signed-normalized range [-1, 1]. +/// Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -32768 and -32767 both represent -1, +/// matching DXGI_FORMAT_R16G16B16A16_SNORM. +/// public partial struct NormalizedShort4 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; private const float MaxPos = 0x7FFF; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector4 Max = Vector128.Create(MaxPos).AsVector4(); private static readonly Vector4 Min = Vector4.Negate(Max); @@ -71,19 +75,30 @@ public NormalizedShort4(float x, float y, float z, float w) [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; + // Offset the exact signed components before division. Mapping an already normalized value through (value + 1) / 2 loses precision near -1 through cancellation. + Vector4 scaled = new( + (short)((this.PackedValue >> 0x00) & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -32768 cannot escape the scaled range. + return (Vector4.Max(scaled, Min) + Max) / ScaledMagnitude; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxPos); + public readonly Vector4 ToVector4() + { + Vector4 vector = new( + (short)((this.PackedValue >> 0x00) & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + + // DirectX SNORM maps both -32768 and -32767 to -1. + return Vector4.Max(vector, Min) / MaxPos; + } /// public static PixelTypeInfo GetPixelTypeInfo() @@ -95,6 +110,61 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use an affine [-1, 1] encoding, so direct multiplication would use the wrong zero point. + vector *= 2F; + vector -= Vector4.One; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromAssociatedVector4(Vector4 source) + { + // Map the affine native encoding to logical [0, 1] space before unassociating. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedShort4 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs index da0e6ec609..da6762912d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,20 @@ public partial struct A8 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // A8 stores alpha without color components, so association cannot alter any emitted or consumed vector component. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.FromUnassociatedVector4Destructive(configuration, source, destination); + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.FromUnassociatedScaledVector4Destructive(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs new file mode 100644 index 0000000000..746fbf85fd --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Abgr32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs new file mode 100644 index 0000000000..80dbd3c318 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Argb32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs index 87175fe21c..737f6999f7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct Bgr565 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs new file mode 100644 index 0000000000..606990d660 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs index c012547c95..7f0919919e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,93 @@ public partial struct Byte4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Protected hooks are same-instance extension points. Cross-format reuse must request the representation through public modifier dispatch. + private const PixelConversionModifiers UnassociatedScaledModifiers = PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; + private const PixelConversionModifiers AssociatedScaledModifiers = PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply; + + private static readonly Vector4 NativeMaximum = new(byte.MaxValue); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Byte4 and Rgba32 have the same packed layout. Reusing the optimized Rgba32 unpacker avoids four scalar byte + // extractions per pixel; the affine transform then restores Byte4's native [0, 255] vector range. + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, UnassociatedScaledModifiers); + Vector4Converters.MultiplyThenAdd(destination, NativeMaximum, Vector4.Zero); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Association is performed in normalized color space before mapping all four components back to Byte4's native range. + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, AssociatedScaledModifiers); + Vector4Converters.MultiplyThenAdd(destination, NativeMaximum, Vector4.Zero); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, UnassociatedScaledModifiers); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, AssociatedScaledModifiers); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Vector4Converters.AddThenDivide(source, Vector4.Zero, NativeMaximum); + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), UnassociatedScaledModifiers); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Native alpha is normalized with RGB so the Rgba32 bulk path can unassociate in a single, consistent coordinate space. + Vector4Converters.AddThenDivide(source, Vector4.Zero, NativeMaximum); + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), AssociatedScaledModifiers); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), UnassociatedScaledModifiers); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), AssociatedScaledModifiers); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index e3ff3a7fea..773b8c1b89 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; - namespace SixLabors.ImageSharp.PixelFormats; /// @@ -45,7 +44,7 @@ public override void FromVector4Destructive( Span 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)); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index fd6465a22f..5c8aeea767 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; - namespace SixLabors.ImageSharp.PixelFormats; /// @@ -45,7 +44,7 @@ public override void FromVector4Destructive( Span 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)); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index d53ed520b6..abf077d152 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/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#>)); } /// @@ -177,7 +181,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeTheseModifiers#>)); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeToModifiers#>)); } <#+ } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs index 770f3a1de8..07428f4845 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct HalfSingle /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs index 160ab9bd0f..e167c4eb7b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct HalfVector2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs index 703138454e..9eae7d5cbc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,91 @@ public partial struct HalfVector4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeToScaledMultiplier = new(HalfTypeHelper.InverseFiniteRange); + private static readonly Vector4 NativeToScaledOffset = new(HalfTypeHelper.ScaledMidpoint); + private static readonly Vector4 ScaledToNativeMultiplier = new(HalfTypeHelper.FiniteRange); + private static readonly Vector4 ScaledToNativeOffset = new(HalfTypeHelper.FiniteMinimum); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // The half-vector layouts are identical, so the shared expansion kernel can process the source without copying it. + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Association uses normalized opacity, not the native binary16 alpha value. + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, ScaledToNativeMultiplier, ScaledToNativeOffset); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToUnassociatedScaledVector4(configuration, source, destination); + Numerics.Premultiply(destination[..source.Length]); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // DirectX half vectors are not normalized formats, so values outside the nominal color range must reach storage unchanged. + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // Restore normalized opacity before unassociating, then return the result to the native binary16 range. + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Numerics.UnPremultiply(source); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Numerics.UnPremultiply(source); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs new file mode 100644 index 0000000000..52b2dbf376 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs @@ -0,0 +1,375 @@ +// 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.Common.Helpers; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct HalfVector4P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + private static readonly Vector4 NativeToScaledMultiplier = new(HalfTypeHelper.InverseFiniteRange); + private static readonly Vector4 NativeToScaledOffset = new(HalfTypeHelper.ScaledMidpoint); + private static readonly Vector4 ScaledToNativeMultiplier = new(HalfTypeHelper.FiniteRange); + private static readonly Vector4 ScaledToNativeOffset = new(HalfTypeHelper.FiniteMinimum); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToUnassociatedScaledVector4(configuration, source, destination); + Vector4Converters.MultiplyThenAdd(destination[..source.Length], ScaledToNativeMultiplier, ScaledToNativeOffset); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToAssociatedScaledVector4(configuration, source, destination); + Numerics.UnPremultiply(destination[..source.Length]); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Associate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Reassociate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Associate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Reassociate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + /// Associates scaled vectors with the alpha values representable by native binary16 storage. + /// + /// The vectors to convert in place. + private static void Associate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); + } + + return; + } + + for (; i < source.Length; i++) + { + Unsafe.Add(ref sourceBase, (uint)i) = HalfVector4P.Associate(Unsafe.Add(ref sourceBase, (uint)i)); + } + } + + /// + /// Reassociates scaled vectors with the alpha values representable by native binary16 storage. + /// + /// The vectors to convert in place. + private static void Reassociate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); + } + + return; + } + + for (; i < source.Length; i++) + { + Unsafe.Add(ref sourceBase, (uint)i) = HalfVector4P.Reassociate(Unsafe.Add(ref sourceBase, (uint)i)); + } + } + + /// + /// Converts an unassociated scaled vector to associated scaled components. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source) + { + source = ClampUnit(source); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = QuantizeScaledAlpha(alpha); + Vector128 result = source * storedAlpha; + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Converts unassociated scaled vectors to associated scaled components. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source) + { + source = ClampUnit(source); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = QuantizeScaledAlpha(alpha); + Vector256 result = source * storedAlpha; + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Converts unassociated scaled vectors to associated scaled components. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source) + { + source = ClampUnit(source); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = QuantizeScaledAlpha(alpha); + Vector512 result = source * storedAlpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Reassociates an associated scaled vector after alpha quantization. + /// + /// The associated vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Reassociate(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = QuantizeScaledAlpha(alpha); + Vector128 result = source * (storedAlpha / alpha); + result = Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates associated scaled vectors after alpha quantization. + /// + /// The associated vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Reassociate(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = QuantizeScaledAlpha(alpha); + Vector256 result = source * (storedAlpha / alpha); + result = Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates associated scaled vectors after alpha quantization. + /// + /// The associated vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Reassociate(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = QuantizeScaledAlpha(alpha); + Vector512 result = source * (storedAlpha / alpha); + Vector512 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); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha lanes. + /// The scaled alpha values represented by binary16 storage. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 QuantizeScaledAlpha(Vector128 alpha) + { + Vector128 native = (ClampUnit(alpha) * Vector128.Create(HalfTypeHelper.FiniteRange)) + Vector128.Create(HalfTypeHelper.FiniteMinimum); + return (HalfTypeHelper.RoundToHalf(native) * Vector128.Create(HalfTypeHelper.InverseFiniteRange)) + Vector128.Create(HalfTypeHelper.ScaledMidpoint); + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha lanes. + /// The scaled alpha values represented by binary16 storage. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 QuantizeScaledAlpha(Vector256 alpha) + { + Vector256 native = (ClampUnit(alpha) * Vector256.Create(HalfTypeHelper.FiniteRange)) + Vector256.Create(HalfTypeHelper.FiniteMinimum); + return (HalfTypeHelper.RoundToHalf(native) * Vector256.Create(HalfTypeHelper.InverseFiniteRange)) + Vector256.Create(HalfTypeHelper.ScaledMidpoint); + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha lanes. + /// The scaled alpha values represented by binary16 storage. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 QuantizeScaledAlpha(Vector512 alpha) + { + Vector512 native = (ClampUnit(alpha) * Vector512.Create(HalfTypeHelper.FiniteRange)) + Vector512.Create(HalfTypeHelper.FiniteMinimum); + return (HalfTypeHelper.RoundToHalf(native) * Vector512.Create(HalfTypeHelper.InverseFiniteRange)) + Vector512.Create(HalfTypeHelper.ScaledMidpoint); + } + + /// + /// Clamps vectors to the scaled color range while preserving NaN lanes. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampUnit(Vector128 source) + { + Vector128 clamped = Vector128.Min(Vector128.Max(source, Vector128.Zero), Vector128.One); + return Vector128.ConditionalSelect(Vector128.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the scaled color range while preserving NaN lanes. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampUnit(Vector256 source) + { + Vector256 clamped = Vector256.Min(Vector256.Max(source, Vector256.Zero), Vector256.One); + return Vector256.ConditionalSelect(Vector256.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the scaled color range while preserving NaN lanes. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampUnit(Vector512 source) + { + Vector512 clamped = Vector512.Min(Vector512.Max(source, Vector512.Zero), Vector512.One); + return Vector512.ConditionalSelect(Vector512.Equals(source, source), clamped, source); + } + + /// + /// Maps associated scaled vectors to native components and packs them as binary16 values. + /// + /// The associated scaled vectors. + /// The destination pixels. + private static void PackAssociatedScaled(Span source, Span destination) + { + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination)); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs index c9714c2170..b79d2a5b21 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct L16 /// /// Provides optimized overrides for bulk operations. /// - internal partial class PixelOperations : PixelOperations; + internal partial class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs index e7b463fe08..fb7945fdf8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct L8 /// /// Provides optimized overrides for bulk operations. /// - internal partial class PixelOperations : PixelOperations; + internal partial class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs index 36f4a0bf55..5568fc742d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +14,25 @@ public partial struct NormalizedByte2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(1F, 1F, 0F, 0F); + private static readonly Vector4 NativeDivisor = new(2F, 2F, 1F, 1F); + + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Only X and Y use affine native coordinates. Preserve W as normalized alpha for the scaled unassociation step. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs index e67321e4f4..d467b802ea 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,89 @@ public partial struct NormalizedByte4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeScale = new(2F); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // The packed P and non-P layouts are identical, so the shared SIMD unpacker supplies normalized values. Association + // happens in [0, 1] before the result is mapped back to the signed-native [-1, 1] coordinate system. + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, NativeScale, -Vector4.One); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true); + Numerics.Premultiply(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), false); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Convert all native components together so alpha and RGB enter unassociation in the same normalized coordinate system. + Vector4Converters.AddThenDivide(source, Vector4.One, NativeScale); + Numerics.UnPremultiply(source); + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Numerics.UnPremultiply(source); + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs new file mode 100644 index 0000000000..de2f8f17b7 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs @@ -0,0 +1,814 @@ +// 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; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct NormalizedByte4P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + private const byte RestorePackedPixelOrder = 0b_11_01_10_00; + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(source, destination[..source.Length], false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToVector4(source, destination[..source.Length], false); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Associate(source, false); + Pack(source, destination, true); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Reassociate(source, false); + Pack(source, destination, true); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(source, destination[..source.Length], true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToVector4(source, destination[..source.Length], true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Associate(source, true); + Pack(source, destination, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Reassociate(source, true); + Pack(source, destination, true); + } + + /// + /// Converts packed signed-normalized pixels to native or scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + /// Whether to produce scaled vectors. + internal static void ToVector4(ReadOnlySpan source, Span destination, bool scaled) + { + ref byte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + // Each packed component occupies one byte, so the destination Vector4 lane count also defines the source byte stride. + int componentsPerPixel = Vector128.Count; + int i = 0; + + // Portable widening advances one integer width at a time, so assemble the wider vectors from ordered 128-bit halves. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)).AsSByte(); + Vector128 lowerShorts = Vector128.WidenLower(packed); + Vector128 upperShorts = Vector128.WidenUpper(packed); + Vector256 lowerIntegers = Vector256.Create(Vector128.WidenLower(lowerShorts), Vector128.WidenUpper(lowerShorts)); + Vector256 upperIntegers = Vector256.Create(Vector128.WidenLower(upperShorts), Vector128.WidenUpper(upperShorts)); + Vector512 integers = Vector512.Create(lowerIntegers, upperIntegers); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Vector512.ConvertToSingle(integers), scaled); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector256 integers = Vector256.Create(Vector128.WidenLower(shorts), Vector128.WidenUpper(shorts)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Vector256.ConvertToSingle(integers), scaled); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + uint packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector128 integers = Vector128.WidenLower(shorts); + Unsafe.Add(ref destinationBase, (uint)i) = ToVector4(Vector128.ConvertToSingle(integers), scaled).AsVector4(); + } + + return; + } + + ref NormalizedByte4P pixelBase = ref Unsafe.As(ref sourceBase); + + for (; i < source.Length; i++) + { + NormalizedByte4P pixel = Unsafe.Add(ref pixelBase, (uint)i); + Unsafe.Add(ref destinationBase, (uint)i) = scaled ? pixel.ToScaledVector4() : pixel.ToVector4(); + } + } + + /// + /// Converts signed-normalized storage to native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToVector4(Vector512 source, bool scaled) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector512.Max(source, Vector512.Create(-MaxPos)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + return (source + Vector512.Create(MaxPos)) / Vector512.Create(ScaledMagnitude); + } + + return source / Vector512.Create(MaxPos); + } + + /// + /// Converts signed-normalized storage to native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToVector4(Vector256 source, bool scaled) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector256.Max(source, Vector256.Create(-MaxPos)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + return (source + Vector256.Create(MaxPos)) / Vector256.Create(ScaledMagnitude); + } + + return source / Vector256.Create(MaxPos); + } + + /// + /// Converts signed-normalized storage to native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToVector4(Vector128 source, bool scaled) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector128.Max(source, Vector128.Create(-MaxPos)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + return (source + Vector128.Create(MaxPos)) / Vector128.Create(ScaledMagnitude); + } + + return source / Vector128.Create(MaxPos); + } + + /// + /// Converts packed associated pixels directly to unassociated native or scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + /// Whether to produce scaled vectors. + private static void ToUnassociatedVector4(ReadOnlySpan source, Span destination, bool scaled) + { + ref byte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + // Each packed component occupies one byte, so the destination Vector4 lane count also defines the source byte stride. + int componentsPerPixel = Vector128.Count; + int i = 0; + + // Portable widening advances one integer width at a time, so assemble the wider vectors from ordered 128-bit halves. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)).AsSByte(); + Vector128 lowerShorts = Vector128.WidenLower(packed); + Vector128 upperShorts = Vector128.WidenUpper(packed); + Vector256 lowerIntegers = Vector256.Create(Vector128.WidenLower(lowerShorts), Vector128.WidenUpper(lowerShorts)); + Vector256 upperIntegers = Vector256.Create(Vector128.WidenLower(upperShorts), Vector128.WidenUpper(upperShorts)); + Vector512 integers = Vector512.Create(lowerIntegers, upperIntegers); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector512.ConvertToSingle(integers), scaled); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector256 integers = Vector256.Create(Vector128.WidenLower(shorts), Vector128.WidenUpper(shorts)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector256.ConvertToSingle(integers), scaled); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + uint packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector128 integers = Vector128.WidenLower(shorts); + Unsafe.Add(ref destinationBase, (uint)i) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers), scaled).AsVector4(); + } + + return; + } + + ref NormalizedByte4P pixelBase = ref Unsafe.As(ref sourceBase); + + for (; i < source.Length; i++) + { + NormalizedByte4P pixel = Unsafe.Add(ref pixelBase, (uint)i); + Unsafe.Add(ref destinationBase, (uint)i) = scaled ? pixel.ToUnassociatedScaledVector4() : pixel.ToUnassociatedVector4(); + } + } + + /// + /// Converts four associated signed-byte vectors to unassociated native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToUnassociatedVector4(Vector512 source, bool scaled) + { + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + source = Vector512.Max(source, Vector512.Create(-MaxPos)); + source += Vector512.Create(MaxPos); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector512 normalizeMask = Vector512.Equals(alpha, Vector512.Zero) | alphaMask; + Vector512 divisor = Vector512.ConditionalSelect(normalizeMask, Vector512.Create(ScaledMagnitude), alpha); + Vector512 result = source / divisor; + + // Unassociation is performed in scaled space; map the result back only after alpha has served as a true opacity value. + return scaled ? result : (result * Vector512.Create(2F)) - Vector512.One; + } + + /// + /// Converts two associated signed-byte vectors to unassociated native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToUnassociatedVector4(Vector256 source, bool scaled) + { + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + source = Vector256.Max(source, Vector256.Create(-MaxPos)); + source += Vector256.Create(MaxPos); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector256 normalizeMask = Vector256.Equals(alpha, Vector256.Zero) | alphaMask; + Vector256 divisor = Vector256.ConditionalSelect(normalizeMask, Vector256.Create(ScaledMagnitude), alpha); + Vector256 result = source / divisor; + + // Unassociation is performed in scaled space; map the result back only after alpha has served as a true opacity value. + return scaled ? result : (result * Vector256.Create(2F)) - Vector256.One; + } + + /// + /// Converts an associated signed-byte vector to an unassociated native or scaled vector. + /// + /// The signed storage components. + /// Whether to produce a scaled vector. + /// The unassociated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToUnassociatedVector4(Vector128 source, bool scaled) + { + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + source = Vector128.Max(source, Vector128.Create(-MaxPos)); + source += Vector128.Create(MaxPos); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + Vector128 normalizeMask = Vector128.Equals(alpha, Vector128.Zero) | alphaMask; + Vector128 divisor = Vector128.ConditionalSelect(normalizeMask, Vector128.Create(ScaledMagnitude), alpha); + Vector128 result = source / divisor; + + // Unassociation is performed in scaled space; map the result back only after alpha has served as a true opacity value. + return scaled ? result : (result * Vector128.Create(2F)) - Vector128.One; + } + + /// + /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The vectors to convert in place. + /// Whether the source contains scaled vectors. + private static void Associate(Span source, bool scaled) + { + // Quantize alpha through signed-normalized destination storage before associating RGB. The kernels replace W separately + // so alpha is stored unchanged instead of being multiplied by itself. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Associate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Associate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Associate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + return; + } + + ref Vector4 tailBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Vector4 vector = Unsafe.Add(ref tailBase, i); + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + vector += Vector4.One; + vector /= 2F; + } + + // Qualify the containing pixel type because this nested class also declares SIMD helpers named Associate. + Unsafe.Add(ref tailBase, i) = NormalizedByte4P.Associate(vector); + } + } + + /// + /// Converts four unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source, bool scaled) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = (source + one) / Vector512.Create(2F); + } + + source = Vector512.Min(Vector512.Max(source, zero), one); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 nativeAlpha = Vector512.Min(Vector512.Max((alpha * Vector512.Create(2F)) - one, -one), one); + Vector512 storedAlpha = Vector512_.RoundToNearestInteger(nativeAlpha * Vector512.Create(MaxPos)); + storedAlpha += Vector512.Create(MaxPos); + storedAlpha /= Vector512.Create(ScaledMagnitude); + Vector512 result = source * storedAlpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts two unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source, bool scaled) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = (source + one) / Vector256.Create(2F); + } + + source = Vector256.Min(Vector256.Max(source, zero), one); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 nativeAlpha = Vector256.Min(Vector256.Max((alpha * Vector256.Create(2F)) - one, -one), one); + Vector256 storedAlpha = Vector256_.RoundToNearestInteger(nativeAlpha * Vector256.Create(MaxPos)); + storedAlpha += Vector256.Create(MaxPos); + storedAlpha /= Vector256.Create(ScaledMagnitude); + Vector256 result = source * storedAlpha; + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts an unassociated native or scaled vector to the destination's associated scaled representation. + /// + /// The unassociated vector. + /// Whether the source contains a scaled vector. + /// The associated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source, bool scaled) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = (source + one) / Vector128.Create(2F); + } + + source = Vector128.Min(Vector128.Max(source, zero), one); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 nativeAlpha = Vector128.Min(Vector128.Max((alpha * Vector128.Create(2F)) - one, -one), one); + Vector128 storedAlpha = Vector128_.RoundToNearestInteger(nativeAlpha * Vector128.Create(MaxPos)); + storedAlpha += Vector128.Create(MaxPos); + storedAlpha /= Vector128.Create(ScaledMagnitude); + Vector128 result = source * storedAlpha; + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + return Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Reassociates native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The vectors to convert in place. + /// Whether the source contains scaled vectors. + private static void Reassociate(Span source, bool scaled) + { + // Preserve straight color across destination-alpha quantization by scaling RGB by storedAlpha / inputAlpha. The kernels + // replace W, clamp RGB to stored alpha, and clear zero-alpha vectors so the stored result remains valid associated color. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Reassociate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Reassociate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Reassociate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + return; + } + + ref Vector4 tailBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Vector4 vector = Unsafe.Add(ref tailBase, i); + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + vector += Vector4.One; + vector /= 2F; + } + + // Qualify the containing pixel type because this nested class also declares SIMD helpers named Reassociate. + Unsafe.Add(ref tailBase, i) = NormalizedByte4P.Reassociate(vector); + } + } + + /// + /// Reassociates four native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Reassociate(Vector512 source, bool scaled) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = (source + one) / Vector512.Create(2F); + } + + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 nativeAlpha = Vector512.Min(Vector512.Max((alpha * Vector512.Create(2F)) - one, -one), one); + Vector512 storedAlpha = Vector512_.RoundToNearestInteger(nativeAlpha * Vector512.Create(MaxPos)); + storedAlpha += Vector512.Create(MaxPos); + storedAlpha /= Vector512.Create(ScaledMagnitude); + Vector512 result = source * (storedAlpha / alpha); + Vector512 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); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates two native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Reassociate(Vector256 source, bool scaled) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = (source + one) / Vector256.Create(2F); + } + + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 nativeAlpha = Vector256.Min(Vector256.Max((alpha * Vector256.Create(2F)) - one, -one), one); + Vector256 storedAlpha = Vector256_.RoundToNearestInteger(nativeAlpha * Vector256.Create(MaxPos)); + storedAlpha += Vector256.Create(MaxPos); + storedAlpha /= Vector256.Create(ScaledMagnitude); + Vector256 result = source * (storedAlpha / alpha); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates a native or scaled vector with the scaled alpha value the destination stores. + /// + /// The associated vector. + /// Whether the source contains a scaled vector. + /// The reassociated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Reassociate(Vector128 source, bool scaled) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = (source + one) / Vector128.Create(2F); + } + + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 nativeAlpha = Vector128.Min(Vector128.Max((alpha * Vector128.Create(2F)) - one, -one), one); + Vector128 storedAlpha = Vector128_.RoundToNearestInteger(nativeAlpha * Vector128.Create(MaxPos)); + storedAlpha += Vector128.Create(MaxPos); + storedAlpha /= Vector128.Create(ScaledMagnitude); + Vector128 result = source * (storedAlpha / alpha); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + result = Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Packs native or scaled vectors into signed-normalized storage. + /// + /// The source vectors. + /// The destination pixels. + /// Whether the source contains scaled vectors. + internal static void Pack(Span source, Span destination, bool scaled) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + ref NormalizedByte4P destinationBase = ref MemoryMarshal.GetReference(destination); + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector512 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector512 integers = ConvertToPackedInt32(vector, scaled); + Vector256 shorts = Vector256_.PackSignedSaturate(integers.GetLower(), integers.GetUpper()); + Vector128 packed = Vector128_.PackSignedSaturate(shorts.GetLower(), shorts.GetUpper()); + + if (Avx2.IsSupported) + { + // AVX2 narrows each 128-bit lane independently, placing the middle two pixels out of order. + packed = Vector128_.ShuffleNative(packed.AsSingle(), RestorePackedPixelOrder).AsSByte(); + } + + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = packed; + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector256 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector256 integers = ConvertToPackedInt32(vector, scaled); + Vector128 shorts = Vector128_.PackSignedSaturate(integers.GetLower(), integers.GetUpper()); + Vector128 packed = Vector128_.PackSignedSaturate(shorts, shorts); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)) = packed.AsUInt64().GetElement(0); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + Vector128 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector128 integers = ConvertToPackedInt32(vector, scaled); + Vector128 shorts = Vector128_.PackSignedSaturate(integers, integers); + Vector128 packed = Vector128_.PackSignedSaturate(shorts, shorts); + Unsafe.Add(ref destinationBase, (uint)i).PackedValue = packed.AsUInt32().GetElement(0); + } + + return; + } + + for (; i < source.Length; i++) + { + Vector4 vector = Unsafe.Add(ref sourceBase, (uint)i); + Unsafe.Add(ref destinationBase, (uint)i) = scaled ? FromScaledVector4(vector) : FromVector4(vector); + } + } + + /// + /// Converts four vectors to signed integers using the destination's packing contract. + /// + /// The source vectors. + /// Whether the source contains scaled vectors. + /// The packed integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertToPackedInt32(Vector512 source, bool scaled) + { + Vector512 one = Vector512.One; + + if (scaled) + { + source *= Vector512.Create(2F); + source -= one; + } + + source = Vector512.Min(Vector512.Max(source, -one), one) * Vector512.Create(MaxPos); + return Vector512_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts two vectors to signed integers using the destination's packing contract. + /// + /// The source vectors. + /// Whether the source contains scaled vectors. + /// The packed integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToPackedInt32(Vector256 source, bool scaled) + { + Vector256 one = Vector256.One; + + if (scaled) + { + source *= Vector256.Create(2F); + source -= one; + } + + source = Vector256.Min(Vector256.Max(source, -one), one) * Vector256.Create(MaxPos); + return Vector256_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts a vector to signed integers using the destination's packing contract. + /// + /// The source vector. + /// Whether the source contains a scaled vector. + /// The packed integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertToPackedInt32(Vector128 source, bool scaled) + { + Vector128 one = Vector128.One; + + if (scaled) + { + source *= Vector128.Create(2F); + source -= one; + } + + source = Vector128.Min(Vector128.Max(source, -one), one) * Vector128.Create(MaxPos); + return Vector128_.ConvertToInt32RoundToEven(source); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs index 99636cb376..f35f2a2dad 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +14,25 @@ public partial struct NormalizedShort2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(1F, 1F, 0F, 0F); + private static readonly Vector4 NativeDivisor = new(2F, 2F, 1F, 1F); + + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Only X and Y use affine native coordinates. Preserve W as normalized alpha for the scaled unassociation step. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs index ad6aa8d8a8..8a60d68b4b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,89 @@ public partial struct NormalizedShort4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeScale = new(2F); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], true, false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Association is defined in scaled color space, not around the signed-native zero point. The shared signed-short SIMD + // kernel handles unpacking before the result is mapped back to the format's native [-1, 1] range. + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true, true); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, NativeScale, -Vector4.One); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], true, true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true, true); + Numerics.Premultiply(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, false); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Convert native RGB and alpha together before unassociation so the subsequent scaled pack retains the scalar contract. + Vector4Converters.AddThenDivide(source, Vector4.One, NativeScale); + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs index 7bca1c781e..5be973404e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct Rg32 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs index c9ed730c47..31816ceb0f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct Rgb48 /// /// Provides optimized overrides for bulk operations. /// - internal partial class PixelOperations : PixelOperations; + internal partial class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs new file mode 100644 index 0000000000..990fd64bd1 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// The alpha component is always fully opaque. +/// +public partial struct Rgb96 +{ + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs new file mode 100644 index 0000000000..19338f12f8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs new file mode 100644 index 0000000000..f3c01e082a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct RgbaHalf +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : PixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // RgbaHalf and RgbaHalfP have the same four-half layout. Sharing the binary16 expansion kernel keeps this path + // vectorized without changing the unassociated meaning of the source components. + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // Association is fused with half expansion so the conversion reads and writes each span once. + RgbaHalfP.PixelOperations.UnpackAssociated(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToAssociatedVector4(configuration, source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // The layouts are identical, so the shared packer can write RgbaHalf storage without an intermediate buffer. + RgbaHalfP.PixelOperations.Pack(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // Unassociation is fused with binary16 packing so processors do not pay for another pass over their vector buffer. + RgbaHalfP.PixelOperations.PackFromAssociated(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromUnassociatedVector4Destructive(configuration, source, destination); + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromAssociatedVector4Destructive(configuration, source, destination); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs new file mode 100644 index 0000000000..825ebeec92 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs @@ -0,0 +1,795 @@ +// 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.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct RgbaHalfP +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + UnpackUnassociated(source, destination[..source.Length]); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Unpack(source, destination[..source.Length]); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToAssociatedVector4(configuration, source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PackUnassociated(source, destination[..source.Length]); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PackAssociated(source, destination[..source.Length]); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromUnassociatedVector4Destructive(configuration, source, destination); + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromAssociatedVector4Destructive(configuration, source, destination); + + /// + /// Expands binary16 components without changing their unit-range representation. + /// + /// The packed source pixels. + /// The destination vectors. + internal static void Unpack(ReadOnlySpan source, Span destination) + { + ref ushort sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref float destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 packed = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector512 lower, Vector512 upper) = HalfTypeHelper.Unpack(packed); + Vector512.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector512.Count)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector256 lower, Vector256 upper) = HalfTypeHelper.Unpack(packed); + Vector256.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector256.Count)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector128 lower, Vector128 upper) = HalfTypeHelper.Unpack(packed); + Vector128.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // Four binary16 components form one pixel, so the only possible remainder is one complete pixel. + ulong remainder = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref sourceBase, (uint)i))); + Vector128 packed = Vector128.CreateScalarUnsafe(remainder).AsUInt16(); + Vector128.StoreUnsafe(HalfTypeHelper.Unpack(packed).Lower, ref destinationBase, (nuint)i); + } + + return; + } + + ref RgbaHalfP pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = Unsafe.Add(ref pixelBase, (uint)pixelIndex).ToVector4(); + } + } + + /// + /// Expands unassociated binary16 components and associates RGB in the same pass. + /// + /// The packed source pixels. + /// The destination vectors. + internal static void UnpackAssociated(ReadOnlySpan source, Span destination) + { + ref ushort sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref float destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 packed = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector512 lower, Vector512 upper) = HalfTypeHelper.Unpack(packed); + Vector512.StoreUnsafe(Associate(lower), ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(Associate(upper), ref destinationBase, (nuint)(i + Vector512.Count)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector256 lower, Vector256 upper) = HalfTypeHelper.Unpack(packed); + Vector256.StoreUnsafe(Associate(lower), ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(Associate(upper), ref destinationBase, (nuint)(i + Vector256.Count)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector128 lower, Vector128 upper) = HalfTypeHelper.Unpack(packed); + Vector128.StoreUnsafe(Associate(lower), ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(Associate(upper), ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // Four binary16 components form one pixel, so the only possible remainder is one complete pixel. + ulong remainder = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref sourceBase, (uint)i))); + Vector128 packed = Vector128.CreateScalarUnsafe(remainder).AsUInt16(); + Vector128.StoreUnsafe(Associate(HalfTypeHelper.Unpack(packed).Lower), ref destinationBase, (nuint)i); + } + + return; + } + + ref RgbaHalfP pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref pixelBase, (uint)pixelIndex).ToVector4(); + Numerics.Premultiply(ref vector); + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = vector; + } + } + + /// + /// Expands associated binary16 components and unassociates RGB in the same pass. + /// + /// The packed source pixels. + /// The destination vectors. + internal static void UnpackUnassociated(ReadOnlySpan source, Span destination) + { + ref ushort sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref float destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 packed = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector512 lower, Vector512 upper) = HalfTypeHelper.Unpack(packed); + Vector512.StoreUnsafe(Unassociate(lower), ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(Unassociate(upper), ref destinationBase, (nuint)(i + Vector512.Count)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector256 lower, Vector256 upper) = HalfTypeHelper.Unpack(packed); + Vector256.StoreUnsafe(Unassociate(lower), ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(Unassociate(upper), ref destinationBase, (nuint)(i + Vector256.Count)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector128 lower, Vector128 upper) = HalfTypeHelper.Unpack(packed); + Vector128.StoreUnsafe(Unassociate(lower), ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(Unassociate(upper), ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // Four binary16 components form one pixel, so the only possible remainder is one complete pixel. + ulong remainder = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref sourceBase, (uint)i))); + Vector128 packed = Vector128.CreateScalarUnsafe(remainder).AsUInt16(); + Vector128.StoreUnsafe(Unassociate(HalfTypeHelper.Unpack(packed).Lower), ref destinationBase, (nuint)i); + } + + return; + } + + ref RgbaHalfP pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = Unsafe.Add(ref pixelBase, (uint)pixelIndex).ToUnassociatedVector4(); + } + } + + /// + /// Packs unassociated unit-range vectors directly into binary16 storage. + /// + /// The source vectors. + /// The destination pixels. + internal static void Pack(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = ClampUnit(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector512 upper = ClampUnit(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = ClampUnit(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector256 upper = ClampUnit(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = ClampUnit(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 upper = ClampUnit(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = ClampUnit(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Numerics.Clamp(Unsafe.Add(ref vectorBase, (uint)pixelIndex), Vector4.Zero, Vector4.One); + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = new RgbaHalfP(vector.X, vector.Y, vector.Z, vector.W); + } + } + + /// + /// Packs vectors directly into IEEE 754 binary16 storage without applying unit-range color constraints. + /// + /// The source vectors. + /// The destination pixels. + internal static void PackUnclamped(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + Vector512 upper = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + Vector256 upper = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + Vector128 upper = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = new RgbaHalfP(vector.X, vector.Y, vector.Z, vector.W); + } + } + + /// + /// Unassociates vectors and packs unassociated unit-range binary16 storage in the same pass. + /// + /// The associated source vectors. + /// The destination pixels. + internal static void PackFromAssociated(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = ClampUnit(Unassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector512 upper = ClampUnit(Unassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = ClampUnit(Unassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector256 upper = ClampUnit(Unassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = ClampUnit(Unassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector128 upper = ClampUnit(Unassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = ClampUnit(Unassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); + Numerics.UnPremultiply(ref vector); + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = new RgbaHalfP(vector.X, vector.Y, vector.Z, vector.W); + } + } + + /// + /// Associates unassociated vectors with their stored binary16 alpha and packs them in one pass. + /// + /// The unassociated source vectors. + /// The destination pixels. + internal static void PackUnassociated(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + // Alpha is rounded to the exact binary16 value that will be stored before RGB is associated with it. + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = AssociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector512 upper = AssociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = AssociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector256 upper = AssociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = AssociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 upper = AssociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = AssociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = RgbaHalfP.FromUnassociatedVector4(Unsafe.Add(ref vectorBase, (uint)pixelIndex)); + } + } + + /// + /// Reassociates vectors with their stored binary16 alpha and packs them in one pass. + /// + /// The associated source vectors. + /// The destination pixels. + internal static void PackAssociated(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + // Scaling RGB by storedAlpha / inputAlpha preserves straight color when binary16 rounds the alpha channel. + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = ReassociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector512 upper = ReassociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = ReassociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector256 upper = ReassociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = ReassociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 upper = ReassociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = ReassociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = RgbaHalfP.FromAssociatedVector4(Unsafe.Add(ref vectorBase, (uint)pixelIndex)); + } + } + + /// + /// Associates RGB with alpha while preserving each alpha lane. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source) + { + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 result = source * alpha; + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), alpha, result); + } + + /// + /// Associates RGB with alpha while preserving each alpha lane. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source) + { + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 result = source * alpha; + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), alpha, result); + } + + /// + /// Associates RGB with alpha while preserving each alpha lane. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source) + { + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 result = source * alpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, alpha, result); + } + + /// + /// Unassociates RGB while preserving each alpha lane. + /// + /// The associated vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Unassociate(Vector128 source) + { + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + return Numerics.UnPremultiply(source, alpha); + } + + /// + /// Unassociates RGB while preserving each alpha lane. + /// + /// The associated vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Unassociate(Vector256 source) + { + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + return Numerics.UnPremultiply(source, alpha); + } + + /// + /// Unassociates RGB while preserving each alpha lane. + /// + /// The associated vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Unassociate(Vector512 source) + { + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + return Numerics.UnPremultiply(source, alpha); + } + + /// + /// Clamps vectors to the unit range represented by the pixel format. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampUnit(Vector128 source) + { + Vector128 clamped = Vector128.Min(Vector128.Max(source, Vector128.Zero), Vector128.One); + + // Ordered comparison is false for NaN, restoring the source lane to match the scalar clamp contract. + return Vector128.ConditionalSelect(Vector128.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the unit range represented by the pixel format. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampUnit(Vector256 source) + { + Vector256 clamped = Vector256.Min(Vector256.Max(source, Vector256.Zero), Vector256.One); + + // Ordered comparison is false for NaN, restoring the source lane to match the scalar clamp contract. + return Vector256.ConditionalSelect(Vector256.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the unit range represented by the pixel format. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampUnit(Vector512 source) + { + Vector512 clamped = Vector512.Min(Vector512.Max(source, Vector512.Zero), Vector512.One); + + // Ordered comparison is false for NaN, restoring the source lane to match the scalar clamp contract. + return Vector512.ConditionalSelect(Vector512.Equals(source, source), clamped, source); + } + + /// + /// Associates unassociated vectors with the alpha value binary16 storage can reproduce. + /// + /// The unassociated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 AssociateForStorage(Vector128 source) + { + source = ClampUnit(source); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = HalfTypeHelper.RoundToHalf(alpha); + Vector128 result = source * storedAlpha; + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Associates unassociated vectors with the alpha value binary16 storage can reproduce. + /// + /// The unassociated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 AssociateForStorage(Vector256 source) + { + source = ClampUnit(source); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = HalfTypeHelper.RoundToHalf(alpha); + Vector256 result = source * storedAlpha; + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Associates unassociated vectors with the alpha value binary16 storage can reproduce. + /// + /// The unassociated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AssociateForStorage(Vector512 source) + { + source = ClampUnit(source); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = HalfTypeHelper.RoundToHalf(alpha); + Vector512 result = source * storedAlpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Reassociates vectors with the alpha value binary16 storage can reproduce. + /// + /// The associated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ReassociateForStorage(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 clampedAlpha = ClampUnit(alpha); + Vector128 storedAlpha = HalfTypeHelper.RoundToHalf(clampedAlpha); + Vector128 result = source * (storedAlpha / alpha); + result = Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates vectors with the alpha value binary16 storage can reproduce. + /// + /// The associated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ReassociateForStorage(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 clampedAlpha = ClampUnit(alpha); + Vector256 storedAlpha = HalfTypeHelper.RoundToHalf(clampedAlpha); + Vector256 result = source * (storedAlpha / alpha); + result = Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates vectors with the alpha value binary16 storage can reproduce. + /// + /// The associated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ReassociateForStorage(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 clampedAlpha = ClampUnit(alpha); + Vector512 storedAlpha = HalfTypeHelper.RoundToHalf(clampedAlpha); + Vector512 result = source * (storedAlpha / alpha); + Vector512 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); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.LessThanOrEqual(alpha, zero), zero, result); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs index 5f9b51af90..d4b868871e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs @@ -23,9 +23,16 @@ public override void From( ReadOnlySpan sourcePixels, Span destinationPixels) { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + destinationPixels = destinationPixels[..sourcePixels.Length]; Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); + // Cross-format conversion uses public dispatch so associated source pixels are unassociated before entering RgbaVector storage. + PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + // RgbaVector.FromScaledVector4 clamps scaled input, so the optimized bulk path must preserve that behavior after unassociating. + Numerics.Clamp(MemoryMarshal.Cast(destinationVectors), 0F, 1F); } /// @@ -38,7 +45,11 @@ public override void FromVector4Destructive( Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); + + // RgbaVector.FromVector4 and FromScaledVector4 both clamp to the representable [0, 1] range. Preserve that scalar + // contract before the zero-copy representation cast so bulk processor output cannot retain HDR or negative values. + Numerics.Clamp(MemoryMarshal.Cast(sourceVectors), 0F, 1F); + MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels[..sourceVectors.Length]); } /// @@ -50,6 +61,7 @@ public override void ToVector4( { Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); + destinationVectors = destinationVectors[..sourcePixels.Length]; MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs index d8225f18aa..4d6b066ad5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +14,26 @@ public partial struct Short2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(-MinNeg, -MinNeg, 0F, 0F); + private static readonly Vector4 NativeRange = new(Range, Range, 1F, 1F); + + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // X and Y use signed-native coordinates while incoming W remains normalized alpha. Map only the stored components before + // the scaled bulk path unassociates RGB; transforming W would change the opacity that must be removed. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeRange); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs index 3d7043b0c8..83c3ce0531 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,90 @@ public partial struct Short4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(-MinNeg); + private static readonly Vector4 NativeMagnitude = new(Range); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], false, false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Signed integer components use an affine native range. Expand to scaled vectors, associate there, then restore the + // native zero point so RGB and alpha remain in the same coordinate system. + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, false, true); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, NativeMagnitude, -NativeOffset); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], false, true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, false, true); + Numerics.Premultiply(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, false); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Normalize all four components before unassociating, then use the scaled packing path to preserve Short4's affine mapping. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeMagnitude); + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index e7c97269e1..a88823069d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -7,11 +7,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two 16-bit unsigned normalized values. /// +/// +/// , , and scaled vector conversions return x and y in [0, 1], z +/// as 0, and implicit alpha as 1. The packed storage layout matches DXGI_FORMAT_R16G16_UNORM. +/// public partial struct Rg32 : IPixel, IPackedVector { private static readonly Vector2 Max = new(ushort.MaxValue); @@ -84,6 +85,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rg32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 190407296c..b8c2d2098b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -108,6 +108,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgb24 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index ed6f57abb3..0a240b369a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -91,6 +91,48 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgb48 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 12275061dd..4b940a6a5f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -91,7 +91,49 @@ public Rgb96(uint r, uint g, uint b) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PixelOperations CreatePixelOperations() => new(); + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index cdee22964d..340e247772 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -7,12 +7,13 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed vector type containing 4 unsigned normalized values ranging from 0 to 1. +/// Packed pixel type containing four unsigned normalized values. /// The x, y and z components use 10 bits, and the w component uses 2 bits. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// /// +/// +/// and scaled vector conversions return all components in [0, 1]. The storage layout matches +/// DXGI_FORMAT_R10G10B10A2_UNORM. +/// public partial struct Rgba1010102 : IPixel, IPackedVector { private static readonly Vector4 Multiplier = new(1023F, 1023F, 1023F, 3F); @@ -86,6 +87,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba1010102 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs index 7fc62b7ffb..86fab4f61f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -95,6 +95,47 @@ public Rgba128(uint r, uint g, uint b, uint a) /// public static PixelOperations CreatePixelOperations() => new(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// public static Rgba128 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 1eafab854f..689d317935 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -220,7 +220,7 @@ public uint PackedValue /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -232,6 +232,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs new file mode 100644 index 0000000000..c7bb17ce87 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs @@ -0,0 +1,273 @@ +// 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; + +/// +/// 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. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Rgba32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Rgba32P(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Rgba32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Rgba32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Rgba32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Rgba32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Rgba32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Rgba32P left, Rgba32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Rgba32P left, Rgba32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Rgba32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToRgba32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToRgba32P(source); + } + + /// + public static Rgba32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Rgba32P other && this.Equals(other); + + /// + public readonly bool Equals(Rgba32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Rgba32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Rgba32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Rgba32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index c73daaf86b..300f2357fb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -8,11 +8,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 65535. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four 16-bit unsigned normalized values. /// +/// +/// Component fields expose storage values in [0, 65535]. and scaled vector conversions +/// return them in [0, 1]. The storage layout matches DXGI_FORMAT_R16G16B16A16_UNORM. +/// [StructLayout(LayoutKind.Sequential)] public partial struct Rgba64 : IPixel, IPackedVector { @@ -212,6 +213,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba64 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs new file mode 100644 index 0000000000..573bfca55f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs @@ -0,0 +1,227 @@ +// 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; + +/// +/// Packed pixel type containing four 16-bit floating-point values typically ranging from 0 to 1. +/// The color components are stored in red, green, blue, and alpha order. +/// +/// +/// and scaled vector conversions return the same component values in the nominal color range +/// [0, 1]. The packed representation is binary-compatible with DXGI_FORMAT_R16G16B16A16_FLOAT. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct RgbaHalf : IPixel, IPackedVector +{ + /// + /// Gets or sets the red component. + /// + public Half R; + + /// + /// Gets or sets the green component. + /// + public Half G; + + /// + /// Gets or sets the blue component. + /// + public Half B; + + /// + /// Gets or sets the alpha component. + /// + public Half A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public RgbaHalf(float r, float g, float b) + : this(r, g, b, 1F) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public RgbaHalf(float r, float g, float b, float a) + { + this.R = (Half)r; + this.G = (Half)g; + this.B = (Half)b; + this.A = (Half)a; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public RgbaHalf(Vector4 vector) + : this(vector.X, vector.Y, vector.Z, vector.W) + { + } + + /// + public ulong PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(RgbaHalf left, RgbaHalf right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(RgbaHalf left, RgbaHalf right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new((float)this.R, (float)this.G, (float)this.B, (float)this.A); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16, 16), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Unassociated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromScaledVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + return new RgbaHalf(source); + } + + /// + public static RgbaHalf FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is RgbaHalf other && this.Equals(other); + + /// + public readonly bool Equals(RgbaHalf other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => FormattableString.Invariant($"RgbaHalf({(float)this.R:#0.##}, {(float)this.G:#0.##}, {(float)this.B:#0.##}, {(float)this.A:#0.##})"); +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs new file mode 100644 index 0000000000..75fe4907ce --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs @@ -0,0 +1,242 @@ +// 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; + +/// +/// Packed pixel type containing four associated 16-bit floating-point values typically ranging from 0 to 1. +/// The color components are stored in red, green, blue, and alpha order. +/// +/// +/// and scaled vector conversions return the same associated component values in the nominal +/// color range [0, 1]. The packed representation is binary-compatible with +/// DXGI_FORMAT_R16G16B16A16_FLOAT. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct RgbaHalfP : IPixel, IPackedVector +{ + /// + /// Gets or sets the associated red component. + /// + public Half R; + + /// + /// Gets or sets the associated green component. + /// + public Half G; + + /// + /// Gets or sets the associated blue component. + /// + public Half B; + + /// + /// Gets or sets the alpha component. + /// + public Half A; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public RgbaHalfP(float r, float g, float b) + : this(r, g, b, 1F) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public RgbaHalfP(float r, float g, float b, float a) + { + this.R = (Half)r; + this.G = (Half)g; + this.B = (Half)b; + this.A = (Half)a; + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public RgbaHalfP(Vector4 vector) + : this() => this = FromAssociatedScaledVector4(vector); + + /// + public ulong PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(RgbaHalfP left, RgbaHalfP right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(RgbaHalfP left, RgbaHalfP right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToUnassociatedScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new((float)this.R, (float)this.G, (float)this.B, (float)this.A); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16, 16), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromUnassociatedScaledVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + + // RGB must be associated with the alpha value that binary16 storage can reproduce, not the higher-precision input alpha. + source.W = HalfTypeHelper.Unpack(HalfTypeHelper.Pack(source.W)); + Numerics.Premultiply(ref source); + return new RgbaHalfP(source.X, source.Y, source.Z, source.W); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromAssociatedScaledVector4(Vector4 source) + { + float alpha = source.W; + + if (alpha <= 0F) + { + return default; + } + + float storedAlpha = HalfTypeHelper.Unpack(HalfTypeHelper.Pack(Numerics.Clamp(alpha, 0F, 1F))); + + // Preserve the represented straight color when binary16 rounds alpha, then restore the associated RGB <= alpha invariant. + source *= storedAlpha / alpha; + source.W = storedAlpha; + Numerics.ClampRgbToAlpha(ref source); + return new RgbaHalfP(source.X, source.Y, source.Z, source.W); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static RgbaHalfP FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is RgbaHalfP other && this.Equals(other); + + /// + public readonly bool Equals(RgbaHalfP other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => FormattableString.Invariant($"RgbaHalfP({(float)this.R:#0.##}, {(float)this.G:#0.##}, {(float)this.B:#0.##}, {(float)this.A:#0.##})"); +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 3f9cab32f6..b1704d5267 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -106,6 +106,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RgbaVector FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 39c853a5e8..d6ab2ac1a3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -8,10 +8,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// Packed pixel type containing two 16-bit signed integer values. -/// -/// Ranges from [-32767, -32767, 0, 1] to [32767, 32767, 0, 1] in vector form. -/// /// +/// +/// and return stored components in [-32768, 32767]. +/// Scaled vector conversions map the full stored range to [0, 1]. The packed storage layout matches +/// DXGI_FORMAT_R16G16_SINT. +/// public partial struct Short2 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; @@ -20,6 +22,9 @@ public partial struct Short2 : IPixel, IPackedVector // Two's complement private const float MinNeg = ~(int)MaxPos; + // Scaled conversions cover every signed 16-bit code, including the asymmetric minimum value. + private const float Range = MaxPos - MinNeg; + private static readonly Vector2 Max = new(MaxPos); private static readonly Vector2 Min = new(MinNeg); @@ -73,8 +78,8 @@ public Short2(float x, float y) public readonly Vector4 ToScaledVector4() { Vector2 scaled = this.ToVector2(); - scaled += new Vector2(32767f); - scaled /= 65534F; + scaled -= Min; + scaled /= Range; return new Vector4(scaled, 0f, 1f); } @@ -92,12 +97,55 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromAssociatedVector4(Vector4 source) + { + // Only the stored color components use the signed native encoding; W remains the scaled source alpha. + source.X = (source.X - MinNeg) / Range; + source.Y = (source.Y - MinNeg) / Range; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short2 FromScaledVector4(Vector4 source) { - Vector2 scaled = new Vector2(source.X, source.Y) * 65534F; - scaled -= new Vector2(32767F); + Vector2 scaled = new Vector2(source.X, source.Y) * Range; + scaled += Min; return new Short2 { PackedValue = Pack(scaled) }; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index b6cece2bef..fc3d3367df 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -8,10 +8,11 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// Packed pixel type containing four 16-bit signed integer values. -/// -/// Ranges from [-37267, -37267, -37267, -37267] to [37267, 37267, 37267, 37267] in vector form. -/// /// +/// +/// returns stored components in [-32768, 32767]. Scaled vector conversions map the full +/// stored range to [0, 1]. The packed storage layout matches DXGI_FORMAT_R16G16B16A16_SINT. +/// public partial struct Short4 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; @@ -20,6 +21,9 @@ public partial struct Short4 : IPixel, IPackedVector // Two's complement private const float MinNeg = ~(int)MaxPos; + // Scaled conversions cover every signed 16-bit code, including the asymmetric minimum value. + private const float Range = MaxPos - MinNeg; + private static readonly Vector4 Max = new(MaxPos); private static readonly Vector4 Min = new(MinNeg); @@ -75,8 +79,8 @@ public Short4(float x, float y, float z, float w) public readonly Vector4 ToScaledVector4() { Vector4 scaled = this.ToVector4(); - scaled += new Vector4(32767f); - scaled /= 65534f; + scaled -= Min; + scaled /= Range; return scaled; } @@ -99,12 +103,67 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use the full asymmetric signed range, so scaled zero must map to -32768 rather than -32767. + vector *= Range; + vector += Min; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromAssociatedVector4(Vector4 source) + { + // Map the full signed native encoding to scaled [0, 1] space before unassociating. + source -= Min; + source /= Range; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short4 FromScaledVector4(Vector4 source) { - source *= 65534F; - source -= new Vector4(32767F); + source *= Range; + source += Min; return FromVector4(source); } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 712d43f760..854b515dc0 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -57,7 +57,7 @@ public virtual void ToArgb32(Configuration configuration, ReadOnlySpan s for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Argb32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Argb32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -123,7 +123,7 @@ public virtual void ToAbgr32(Configuration configuration, ReadOnlySpan s for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Abgr32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -189,7 +189,7 @@ public virtual void ToBgr24(Configuration configuration, ReadOnlySpan so for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Bgr24.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -255,7 +255,7 @@ public virtual void ToBgra32(Configuration configuration, ReadOnlySpan s for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Bgra32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -321,7 +321,7 @@ public virtual void ToL8(Configuration configuration, ReadOnlySpan sourc for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = L8.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = L8.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -387,7 +387,7 @@ public virtual void ToL16(Configuration configuration, ReadOnlySpan sour for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = L16.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = L16.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -453,7 +453,7 @@ public virtual void ToLa16(Configuration configuration, ReadOnlySpan sou for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = La16.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = La16.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -519,7 +519,7 @@ public virtual void ToLa32(Configuration configuration, ReadOnlySpan sou for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = La32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = La32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -585,7 +585,7 @@ public virtual void ToRgb24(Configuration configuration, ReadOnlySpan so for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgb24.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -651,7 +651,7 @@ public virtual void ToRgba32(Configuration configuration, ReadOnlySpan s for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgba32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -717,7 +717,7 @@ public virtual void ToRgb48(Configuration configuration, ReadOnlySpan so for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgb48.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -783,7 +783,7 @@ public virtual void ToRgba64(Configuration configuration, ReadOnlySpan s for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgba64.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -849,7 +849,7 @@ public virtual void ToBgra5551(Configuration configuration, ReadOnlySpan for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Bgra5551.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index b2697cb4e4..769f3b44ba 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -69,7 +69,7 @@ for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = <#=pixelType#>.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = <#=pixelType#>.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index ea970a7181..87957a995a 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -5,14 +5,19 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats; /// -/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations -/// for pixel buffers of type . +/// Provides bulk conversion operations for pixel buffers of type . /// +/// +/// The default conversion behavior is suitable for pixel formats that store unassociated alpha. +/// Pixel formats that store associated alpha should derive their operations from +/// . +/// /// The pixel format. public partial class PixelOperations where TPixel : unmanaged, IPixel @@ -26,6 +31,90 @@ public partial class PixelOperations public static PixelOperations Instance => LazyInstance.Value; #pragma warning restore CA1000 // Do not declare static members on generic types + /// + /// Converts pixels to unassociated vectors in the pixel format's native numeric range. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.UnPremultiply); + + /// + /// Converts pixels to associated vectors in the pixel format's native numeric range. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Premultiply); + + /// + /// Converts pixels to the unassociated scaled vector representation used by color operations. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToUnassociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + /// + /// Converts pixels to associated scaled vectors. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToAssociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + /// + /// Converts unassociated vectors in the pixel format's native numeric range to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.UnPremultiply); + + /// + /// Converts associated vectors in the pixel format's native numeric range to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Premultiply); + + /// + /// Converts unassociated scaled vectors to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromUnassociatedScaledVector4Destructive( + Configuration configuration, + Span source, + Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + /// + /// Converts associated scaled vectors to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromAssociatedScaledVector4Destructive( + Configuration configuration, + Span source, + Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + /// /// Gets the pixel type info for the given . /// @@ -33,17 +122,16 @@ public partial class PixelOperations public PixelTypeInfo GetPixelTypeInfo() => TPixel.GetPixelTypeInfo(); /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . + /// Converts vectors to pixels using the numeric range, alpha representation, and companding specified by . + /// The contents of are not preserved. /// /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. + /// Callers must not rely on the contents of after this method returns. /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - /// The to apply during the conversion + /// The configuration. + /// The source vectors. Their contents may be modified during conversion. + /// The destination pixels. + /// The representation of the source vectors and the transformations applied before storing the pixels. public virtual void FromVector4Destructive( Configuration configuration, Span sourceVectors, @@ -51,20 +139,63 @@ public virtual void FromVector4Destructive( PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); - Utils.Vector4Converters.Default.FromVector4(sourceVectors, destination, modifiers); + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Transfer functions operate on straight color components, so restore straight RGB before compressing associated input. + if (associated) + { + Numerics.UnPremultiply(sourceVectors); + } + + SRgbCompanding.Compress(sourceVectors); + + if (scaled) + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.FromAssociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + } + else if (associated) + { + this.FromAssociatedVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } } /// /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . + /// The contents of are not preserved. /// /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. + /// Callers must not rely on the contents of after this method returns. /// /// A to configure internal operations - /// The to the source vectors. + /// The source vectors. Their contents may be modified during conversion. /// The to the destination colors. public void FromVector4Destructive( Configuration configuration, @@ -73,12 +204,12 @@ public void FromVector4Destructive( => this.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.None); /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// Converts pixels to vectors using the numeric range, alpha representation, and companding specified by . /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - /// The to apply during the conversion + /// The configuration. + /// The source pixels. + /// The destination vectors. + /// The requested vector representation and the transformations applied after reading the pixels. public virtual void ToVector4( Configuration configuration, ReadOnlySpan source, @@ -86,8 +217,53 @@ public virtual void ToVector4( PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationVectors, nameof(destinationVectors)); + + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); - Utils.Vector4Converters.Default.ToVector4(source, destinationVectors, modifiers); + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Expand straight RGB first because a transfer function applied to associated components would make color depend on alpha. + if (scaled) + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } + + Span converted = destinationVectors[..source.Length]; + SRgbCompanding.Expand(converted); + + if (associated) + { + Numerics.Premultiply(converted); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.ToAssociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + } + else if (associated) + { + this.ToAssociatedVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } } /// @@ -103,10 +279,10 @@ public void ToVector4( => this.ToVector4(configuration, source, destinationVectors, PixelConversionModifiers.None); /// - /// Bulk operation that copies the to in - /// format. + /// Bulk operation that converts pixels from format to + /// destination pixels. /// - /// The destination pixel type. + /// The source pixel type. /// A to configure internal operations. /// The to the source pixels. /// The to the destination pixels. @@ -121,13 +297,14 @@ public virtual void From( using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); Span vectorSpan = tempVectors.GetSpan(); + for (int i = 0; i < numberOfSlices; i++) { int start = i * sliceLength; ReadOnlySpan s = source.Slice(start, sliceLength); Span d = destination.Slice(start, sliceLength); - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } int endOfCompleteSlices = numberOfSlices * sliceLength; @@ -137,8 +314,8 @@ public virtual void From( ReadOnlySpan s = source[endOfCompleteSlices..]; Span d = destination[endOfCompleteSlices..]; vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } @@ -159,6 +336,13 @@ public virtual void To( 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(source).CopyTo(destination); + return; + } + PixelOperations.Instance.From(configuration, source, destination); } diff --git a/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs new file mode 100644 index 0000000000..4bf8c5e265 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs @@ -0,0 +1,415 @@ +// 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; + +/// +/// Provides shared SIMD conversion for pixel layouts containing four signed 16-bit components. +/// +internal static class SignedShort4PixelOperations +{ + private const byte RestorePackedPixelOrder = 0b_11_01_10_00; + private const float ShortMaximum = short.MaxValue; + private const float SignedNormalizedMagnitude = ShortMaximum * 2F; + private const float SignedIntegerMinimum = short.MinValue; + private const float SignedIntegerRange = ushort.MaxValue; + + /// + /// Expands signed 16-bit components to native or scaled vectors. + /// + /// The source components. Each consecutive group of four components represents one pixel. + /// The destination vectors. + /// Whether native components use signed-normalized values rather than integer values. + /// Whether to map native components to the scaled range. + internal static void ToVector4(ReadOnlySpan source, Span destination, bool normalized, bool scaled) + { + ref short sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + int index = 0; + + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; index <= destination.Length - pixelsPerVector; index += pixelsPerVector) + { + // Packed shorts occupy half the expanded width, so one 256-bit load feeds a complete 512-bit conversion. + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)(index * Vector128.Count)); + Vector512 integers = Vector512.Create(Vector256.WidenLower(packed), Vector256.WidenUpper(packed)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)index)) = ConvertToVector4(Vector512.ConvertToSingle(integers), normalized, scaled); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; index <= destination.Length - pixelsPerVector; index += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(index * Vector128.Count)); + Vector256 integers = Vector256.Create(Vector128.WidenLower(packed), Vector128.WidenUpper(packed)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)index)) = ConvertToVector4(Vector256.ConvertToSingle(integers), normalized, scaled); + } + } + + if (Vector128.IsHardwareAccelerated) + { + ref byte sourceBytes = ref Unsafe.As(ref sourceBase); + + for (; index < destination.Length; index++) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBytes, (uint)(index * sizeof(ulong)))); + Vector128 integers = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsInt16()); + Unsafe.Add(ref destinationBase, (uint)index) = ConvertToVector4(Vector128.ConvertToSingle(integers), normalized, scaled).AsVector4(); + } + + return; + } + + // The fallback preserves the format implementations' operation order on platforms without vector acceleration. + for (; index < destination.Length; index++) + { + int componentIndex = index * Vector128.Count; + Vector4 vector = new(source[componentIndex], source[componentIndex + 1], source[componentIndex + 2], source[componentIndex + 3]); + + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + vector = Vector4.Max(vector, new Vector4(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + vector += new Vector4(ShortMaximum); + vector /= SignedNormalizedMagnitude; + } + else + { + vector /= ShortMaximum; + } + } + else if (scaled) + { + // SINT uses every two's-complement code, unlike SNORM where both minimum encodings represent -1. + vector -= new Vector4(SignedIntegerMinimum); + vector /= SignedIntegerRange; + } + + Unsafe.Add(ref destinationBase, (uint)index) = vector; + } + } + + /// + /// Packs native or scaled vectors into signed 16-bit components. + /// + /// The source vectors. + /// The destination components. Each consecutive group of four components represents one pixel. + /// Whether native components use signed-normalized values rather than integer values. + /// Whether the source vectors use the scaled range. + internal static void FromVector4(Span source, Span destination, bool normalized, bool scaled) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + ref short destinationBase = ref MemoryMarshal.GetReference(destination); + int index = 0; + + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; index <= source.Length - pixelsPerVector; index += pixelsPerVector) + { + Vector512 vectors = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)index)); + Vector512 integers = ConvertToInt32(vectors, normalized, scaled); + + // AVX2 packing operates independently in each 128-bit lane, producing pixels 0, 2, 1, 3. Restore the + // source order with a native lane permutation; the portable PackSignedSaturate fallback is already ordered. + Vector256 packed = Vector256_.PackSignedSaturate(integers.GetLower(), integers.GetUpper()); + + if (Avx2.IsSupported) + { + packed = Avx2.Permute4x64(packed.AsInt64(), RestorePackedPixelOrder).AsInt16(); + } + + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)(index * Vector128.Count))) = packed; + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; index <= source.Length - pixelsPerVector; index += pixelsPerVector) + { + Vector256 vectors = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)index)); + Vector256 integers = ConvertToInt32(vectors, normalized, scaled); + Vector128 packed = Vector128_.PackSignedSaturate(integers.GetLower(), integers.GetUpper()); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)(index * Vector128.Count))) = packed; + } + } + + if (Vector128.IsHardwareAccelerated) + { + ref byte destinationBytes = ref Unsafe.As(ref destinationBase); + + for (; index < source.Length; index++) + { + Vector128 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)index)); + Vector128 integers = ConvertToInt32(vector, normalized, scaled); + Vector128 packed = Vector128_.PackSignedSaturate(integers, integers); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationBytes, (uint)(index * sizeof(ulong))), packed.AsUInt64().GetElement(0)); + } + + return; + } + + for (; index < source.Length; index++) + { + Vector4 vector = Unsafe.Add(ref sourceBase, (uint)index); + + if (normalized) + { + if (scaled) + { + vector *= 2F; + vector -= Vector4.One; + } + + vector *= ShortMaximum; + vector = Numerics.Clamp(vector, new Vector4(-ShortMaximum), new Vector4(ShortMaximum)); + } + else + { + if (scaled) + { + vector *= SignedIntegerRange; + vector += new Vector4(SignedIntegerMinimum); + } + + vector = Numerics.Clamp(vector, new Vector4(short.MinValue), new Vector4(short.MaxValue)); + } + + int componentIndex = index * Vector128.Count; + destination[componentIndex] = (short)MathF.Round(vector.X); + destination[componentIndex + 1] = (short)MathF.Round(vector.Y); + destination[componentIndex + 2] = (short)MathF.Round(vector.Z); + destination[componentIndex + 3] = (short)MathF.Round(vector.W); + } + } + + /// + /// Converts signed integer components to native or scaled vectors while preserving the scalar conversion order. + /// + /// The signed integer components. + /// Whether native components use signed-normalized values. + /// Whether to map native components to the scaled range. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertToVector4(Vector512 source, bool normalized, bool scaled) + { + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector512.Max(source, Vector512.Create(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector512.Create(ShortMaximum); + source /= Vector512.Create(SignedNormalizedMagnitude); + } + else + { + source /= Vector512.Create(ShortMaximum); + } + } + else if (scaled) + { + source -= Vector512.Create(SignedIntegerMinimum); + source /= Vector512.Create(SignedIntegerRange); + } + + return source; + } + + /// + /// Converts signed integer components to native or scaled vectors while preserving the scalar conversion order. + /// + /// The signed integer components. + /// Whether native components use signed-normalized values. + /// Whether to map native components to the scaled range. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToVector4(Vector256 source, bool normalized, bool scaled) + { + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector256.Max(source, Vector256.Create(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector256.Create(ShortMaximum); + source /= Vector256.Create(SignedNormalizedMagnitude); + } + else + { + source /= Vector256.Create(ShortMaximum); + } + } + else if (scaled) + { + source -= Vector256.Create(SignedIntegerMinimum); + source /= Vector256.Create(SignedIntegerRange); + } + + return source; + } + + /// + /// Converts signed integer components to native or scaled vectors while preserving the scalar conversion order. + /// + /// The signed integer components. + /// Whether native components use signed-normalized values. + /// Whether to map native components to the scaled range. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertToVector4(Vector128 source, bool normalized, bool scaled) + { + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector128.Max(source, Vector128.Create(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector128.Create(ShortMaximum); + source /= Vector128.Create(SignedNormalizedMagnitude); + } + else + { + source /= Vector128.Create(ShortMaximum); + } + } + else if (scaled) + { + source -= Vector128.Create(SignedIntegerMinimum); + source /= Vector128.Create(SignedIntegerRange); + } + + return source; + } + + /// + /// Converts vectors to signed integer components using the format's packing contract. + /// + /// The source vectors. + /// Whether native components use signed-normalized values. + /// Whether the source vectors use the scaled range. + /// The converted integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertToInt32(Vector512 source, bool normalized, bool scaled) + { + if (normalized) + { + if (scaled) + { + source *= Vector512.Create(2F); + source -= Vector512.One; + } + + source *= Vector512.Create(ShortMaximum); + source = Vector512.Min(Vector512.Max(source, Vector512.Create(-ShortMaximum)), Vector512.Create(ShortMaximum)); + } + else + { + if (scaled) + { + source *= Vector512.Create(SignedIntegerRange); + source += Vector512.Create(SignedIntegerMinimum); + } + + source = Vector512.Min(Vector512.Max(source, Vector512.Create((float)short.MinValue)), Vector512.Create((float)short.MaxValue)); + } + + return Vector512_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts vectors to signed integer components using the format's packing contract. + /// + /// The source vectors. + /// Whether native components use signed-normalized values. + /// Whether the source vectors use the scaled range. + /// The converted integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 source, bool normalized, bool scaled) + { + if (normalized) + { + if (scaled) + { + source *= Vector256.Create(2F); + source -= Vector256.One; + } + + source *= Vector256.Create(ShortMaximum); + source = Vector256.Min(Vector256.Max(source, Vector256.Create(-ShortMaximum)), Vector256.Create(ShortMaximum)); + } + else + { + if (scaled) + { + source *= Vector256.Create(SignedIntegerRange); + source += Vector256.Create(SignedIntegerMinimum); + } + + source = Vector256.Min(Vector256.Max(source, Vector256.Create((float)short.MinValue)), Vector256.Create((float)short.MaxValue)); + } + + return Vector256_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts vectors to signed integer components using the format's packing contract. + /// + /// The source vectors. + /// Whether native components use signed-normalized values. + /// Whether the source vectors use the scaled range. + /// The converted integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertToInt32(Vector128 source, bool normalized, bool scaled) + { + if (normalized) + { + if (scaled) + { + source *= Vector128.Create(2F); + source -= Vector128.One; + } + + source *= Vector128.Create(ShortMaximum); + source = Vector128.Min(Vector128.Max(source, Vector128.Create(-ShortMaximum)), Vector128.Create(ShortMaximum)); + } + else + { + if (scaled) + { + source *= Vector128.Create(SignedIntegerRange); + source += Vector128.Create(SignedIntegerMinimum); + } + + source = Vector128.Min(Vector128.Max(source, Vector128.Create((float)short.MinValue)), Vector128.Create((float)short.MaxValue)); + } + + return Vector128_.ConvertToInt32RoundToEven(source); + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs new file mode 100644 index 0000000000..0096085e9e --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs @@ -0,0 +1,134 @@ +// 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.Utils; + +internal static partial class Vector4Converters +{ + /// + /// Multiplies each vector component and then adds the corresponding offset component. + /// + /// The vectors to transform in place. + /// The component-wise multiplier. + /// The component-wise offset applied after multiplication. + internal static void MultiplyThenAdd(Span vectors, Vector4 multiplier, Vector4 offset) + { + ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); + int index = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerVector = Vector512.Count / Vector128.Count; + Vector256 multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128()); + Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); + Vector512 multiplier512 = Vector512.Create(multiplier256, multiplier256); + Vector512 offset512 = Vector512.Create(offset256, offset256); + + for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + vector = (vector * multiplier512) + offset512; + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerVector = Vector256.Count / Vector128.Count; + Vector256 multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128()); + Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); + + for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + vector = (vector * multiplier256) + offset256; + } + } + + if (Vector128.IsHardwareAccelerated) + { + Vector128 multiplier128 = multiplier.AsVector128(); + Vector128 offset128 = offset.AsVector128(); + + for (; index < vectors.Length; index++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + vector = (vector * multiplier128) + offset128; + } + + return; + } + + // The scalar fallback retains the same multiply-then-add order as the SIMD paths and the per-pixel contracts. + for (; index < vectors.Length; index++) + { + ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); + vector = (vector * multiplier) + offset; + } + } + + /// + /// Adds the corresponding offset component and then divides each vector component by its divisor. + /// + /// The vectors to transform in place. + /// The component-wise offset applied before division. + /// The component-wise divisor. + internal static void AddThenDivide(Span vectors, Vector4 offset, Vector4 divisor) + { + ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); + int index = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerVector = Vector512.Count / Vector128.Count; + Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); + Vector256 divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128()); + Vector512 offset512 = Vector512.Create(offset256, offset256); + Vector512 divisor512 = Vector512.Create(divisor256, divisor256); + + for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + vector = (vector + offset512) / divisor512; + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerVector = Vector256.Count / Vector128.Count; + Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); + Vector256 divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128()); + + for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + vector = (vector + offset256) / divisor256; + } + } + + if (Vector128.IsHardwareAccelerated) + { + Vector128 offset128 = offset.AsVector128(); + Vector128 divisor128 = divisor.AsVector128(); + + for (; index < vectors.Length; index++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + vector = (vector + offset128) / divisor128; + } + + return; + } + + // Native-to-scaled conversion deliberately adds before dividing to match each format's scalar conversion order. + for (; index < vectors.Length; index++) + { + ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); + vector = (vector + offset) / divisor; + } + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs new file mode 100644 index 0000000000..b47779df8d --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -0,0 +1,892 @@ +// 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.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// Contains . +/// +internal static partial class Vector4Converters +{ + /// + /// Provides efficient batched conversion for four-byte pixel types that store premultiplied alpha. + /// + public static class AssociatedRgbaCompatible + { + /// + /// Converts associated RGBA byte components to an unassociated scaled vector. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + /// The unassociated scaled vector. + [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); + float colorDivisor = alpha == 0 ? byte.MaxValue : alpha; + return vector / new Vector4(colorDivisor, colorDivisor, colorDivisor, byte.MaxValue); + } + + /// + /// Converts packed RGBA byte magnitudes to unassociated scaled vectors. + /// + /// The packed RGBA bytes. + /// The destination vectors. + private static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + ref byte sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + // Each packed component occupies one byte, so the destination Vector4 lane count also defines the source byte stride. + int componentsPerPixel = Vector128.Count; + int i = 0; + + // Portable widening advances one integer width at a time, so assemble the wider vectors from ordered 128-bit halves. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)); + Vector128 lowerShorts = Vector128.WidenLower(packed); + Vector128 upperShorts = Vector128.WidenUpper(packed); + Vector256 lowerIntegers = Vector256.Create(Vector128.WidenLower(lowerShorts), Vector128.WidenUpper(lowerShorts)); + Vector256 upperIntegers = Vector256.Create(Vector128.WidenLower(upperShorts), Vector128.WidenUpper(upperShorts)); + Vector512 integers = Vector512.Create(lowerIntegers, upperIntegers).AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector512.ConvertToSingle(integers)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); + Vector256 integers = Vector256.Create(Vector128.WidenLower(shorts), Vector128.WidenUpper(shorts)).AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector256.ConvertToSingle(integers)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + int pixelsPerVector = Vector128.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)); + (Vector128 shorts0, Vector128 shorts1) = Vector128.Widen(packed); + (Vector128 integers0, Vector128 integers1) = Vector128.Widen(shorts0); + (Vector128 integers2, Vector128 integers3) = Vector128.Widen(shorts1); + + Unsafe.Add(ref destinationBase, (uint)i) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers0.AsInt32())).AsVector4(); + Unsafe.Add(ref destinationBase, (uint)i + 1u) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers1.AsInt32())).AsVector4(); + Unsafe.Add(ref destinationBase, (uint)i + 2u) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers2.AsInt32())).AsVector4(); + Unsafe.Add(ref destinationBase, (uint)i + 3u) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers3.AsInt32())).AsVector4(); + } + } + + for (; i < destination.Length; i++) + { + int offset = i * componentsPerPixel; + + Unsafe.Add(ref destinationBase, (uint)i) = ToUnassociatedVector4( + Unsafe.Add(ref sourceBase, (uint)offset), + Unsafe.Add(ref sourceBase, (uint)offset + 1u), + Unsafe.Add(ref sourceBase, (uint)offset + 2u), + Unsafe.Add(ref sourceBase, (uint)offset + 3u)); + } + } + + /// + /// Converts four associated RGBA byte-magnitude vectors to unassociated scaled vectors. + /// + /// The associated byte-magnitude vectors. + /// The unassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToUnassociatedVector4(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + + // Zero-alpha storage has no alpha divisor, while W always uses the byte normalization divisor. + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector512 normalizeMask = Vector512.Equals(alpha, zero) | alphaMask; + Vector512 divisor = Vector512.ConditionalSelect(normalizeMask, byteMax, alpha); + return source / divisor; + } + + /// + /// Converts two associated RGBA byte-magnitude vectors to unassociated scaled vectors. + /// + /// The associated byte-magnitude vectors. + /// The unassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToUnassociatedVector4(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 byteMax = Vector256.Create((float)byte.MaxValue); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + + // Zero-alpha storage has no alpha divisor, while W always uses the byte normalization divisor. + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector256 normalizeMask = Vector256.Equals(alpha, zero) | alphaMask; + Vector256 divisor = Vector256.ConditionalSelect(normalizeMask, byteMax, alpha); + return source / divisor; + } + + /// + /// Converts an associated RGBA byte-magnitude vector to an unassociated scaled vector. + /// + /// The associated byte-magnitude vector. + /// The unassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToUnassociatedVector4(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 byteMax = Vector128.Create((float)byte.MaxValue); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + + // Zero-alpha storage has no alpha divisor, while W always uses the byte normalization divisor. + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + Vector128 normalizeMask = Vector128.Equals(alpha, zero) | alphaMask; + Vector128 divisor = Vector128.ConditionalSelect(normalizeMask, byteMax, alpha); + return source / divisor; + } + + /// + /// Converts unassociated scaled vectors to associated byte magnitudes. + /// + /// The vectors to convert in place. + private static void AssociateToByte(Span source) + { + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = AssociateToByte(sourceBase); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = AssociateToByte(sourceBase); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = AssociateToByte(sourceBase); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + return; + } + + ref Vector4 tailBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref tailBase, i) = AssociateToByte(Unsafe.Add(ref tailBase, i)); + } + } + + /// + /// Converts four unassociated scaled vectors to associated byte magnitudes. + /// + /// The unassociated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AssociateToByte(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + source = Vector512.Min(Vector512.Max(source, zero), one); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = Vector512.Floor((alpha * byteMax) + Vector512.Create(.5F)); + Vector512 result = source * storedAlpha; + + // RGB is associated using the quantized alpha byte, while W stores that byte directly. + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts two unassociated scaled vectors to associated byte magnitudes. + /// + /// The unassociated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 AssociateToByte(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + Vector256 byteMax = Vector256.Create((float)byte.MaxValue); + source = Vector256.Min(Vector256.Max(source, zero), one); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = Vector256.Floor((alpha * byteMax) + Vector256.Create(.5F)); + Vector256 result = source * storedAlpha; + + // RGB is associated using the quantized alpha byte, while W stores that byte directly. + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts an unassociated scaled vector to associated byte magnitudes. + /// + /// The unassociated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 AssociateToByte(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + Vector128 byteMax = Vector128.Create((float)byte.MaxValue); + source = Vector128.Min(Vector128.Max(source, zero), one); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = Vector128.Floor((alpha * byteMax) + Vector128.Create(.5F)); + Vector128 result = source * storedAlpha; + + // RGB is associated using the quantized alpha byte, while W stores that byte directly. + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + return Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts an unassociated scaled vector to associated byte magnitudes. + /// + /// The unassociated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 AssociateToByte(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + float storedAlpha = (byte)Numerics.Clamp((source.W * byte.MaxValue) + 0.5F, 0, byte.MaxValue); + + source *= storedAlpha; + source.W = storedAlpha; + return source; + } + + /// + /// Converts an unassociated scaled vector to an pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Rgba32P FromUnassociatedVector4ToRgba32P(Vector4 source) + { + source = AssociateToByte(source); + return new Rgba32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an unassociated scaled vector to a pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Bgra32P FromUnassociatedVector4ToBgra32P(Vector4 source) + { + source = AssociateToByte(source); + return new Bgra32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an unassociated scaled vector to an pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Argb32P FromUnassociatedVector4ToArgb32P(Vector4 source) + { + source = AssociateToByte(source); + return new Argb32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an unassociated scaled vector to an pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Abgr32P FromUnassociatedVector4ToAbgr32P(Vector4 source) + { + source = AssociateToByte(source); + return new Abgr32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an associated scaled vector to associated byte magnitudes using the alpha byte the destination stores. + /// + /// The associated scaled vector. + /// The associated byte magnitudes. + [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; + } + else + { + // 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; + Numerics.ClampRgbToAlpha(ref source); + return source; + } + + /// + /// Converts associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores. + /// + /// The vectors to convert in place. + private static void ReassociateToByte(Span source) + { + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + return; + } + + 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)); + } + } + + /// + /// Converts four associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores. + /// + /// The associated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ReassociateToByte(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + Vector512 alpha = Vector512.Max(Vector512_.ShuffleNative(source, 0b_11_11_11_11), zero); + Vector512 byteAlpha = alpha * byteMax; + Vector512 storedAlpha = Vector512.Floor(Vector512.Min(Vector512.Max(byteAlpha + Vector512.Create(.5F), zero), byteMax)); + Vector512 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 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); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.Equals(alpha, zero), zero, result); + } + + /// + /// Converts two associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores. + /// + /// The associated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ReassociateToByte(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 byteMax = Vector256.Create((float)byte.MaxValue); + Vector256 alpha = Vector256.Max(Vector256_.ShuffleNative(source, 0b_11_11_11_11), zero); + Vector256 byteAlpha = alpha * byteMax; + Vector256 storedAlpha = Vector256.Floor(Vector256.Min(Vector256.Max(byteAlpha + Vector256.Create(.5F), zero), byteMax)); + Vector256 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 = Vector256.ConditionalSelect(Vector256.Equals(byteAlpha, storedAlpha), source * byteMax, result); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.Equals(alpha, zero), zero, result); + } + + /// + /// Converts an associated scaled vector to associated byte magnitudes using the alpha byte the destination stores. + /// + /// The associated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ReassociateToByte(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 byteMax = Vector128.Create((float)byte.MaxValue); + Vector128 alpha = Vector128.Max(Vector128_.ShuffleNative(source, 0b_11_11_11_11), zero); + Vector128 byteAlpha = alpha * byteMax; + Vector128 storedAlpha = Vector128.Floor(Vector128.Min(Vector128.Max(byteAlpha + Vector128.Create(.5F), zero), byteMax)); + Vector128 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 = Vector128.ConditionalSelect(Vector128.Equals(byteAlpha, storedAlpha), source * byteMax, result); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + result = Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.Equals(alpha, zero), zero, result); + } + + /// + /// Converts an associated scaled vector to an pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [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)); + } + + /// + /// Converts an associated scaled vector to a pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [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)); + } + + /// + /// Converts an associated scaled vector to an pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [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)); + } + + /// + /// Converts an associated scaled vector to an pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [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)); + } + + /// + /// Converts associated RGBA pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts premultiplied RGBA pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + /// Converts unassociated scaled vectors to premultiplied RGBA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts associated scaled vectors to premultiplied RGBA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts associated BGRA pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + 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 temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); + destination[lastIndex] = source[lastIndex].ToVector4(); + } + + /// + /// Converts premultiplied BGRA pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // Stage all but the final pixel as RGBA bytes in the unwritten end of the expanding Vector4 destination. + // The final expanded vector overlaps that staging region, so convert it directly after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + ToUnassociatedVector4(MemoryMarshal.Cast(temporary), destination[..lastIndex]); + destination[lastIndex] = ToUnassociatedVector4(source[lastIndex].R, source[lastIndex].G, source[lastIndex].B, source[lastIndex].A); + } + + /// + /// Converts unassociated scaled vectors to premultiplied BGRA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied BGRA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated ARGB pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + 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 temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); + destination[lastIndex] = source[lastIndex].ToVector4(); + } + + /// + /// Converts premultiplied ARGB pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // Stage all but the final pixel as RGBA bytes in the unwritten end of the expanding Vector4 destination. + // The final expanded vector overlaps that staging region, so convert it directly after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + ToUnassociatedVector4(MemoryMarshal.Cast(temporary), destination[..lastIndex]); + destination[lastIndex] = ToUnassociatedVector4(source[lastIndex].R, source[lastIndex].G, source[lastIndex].B, source[lastIndex].A); + } + + /// + /// Converts unassociated scaled vectors to premultiplied ARGB pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied ARGB pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated ABGR pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + 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 temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); + destination[lastIndex] = source[lastIndex].ToVector4(); + } + + /// + /// Converts premultiplied ABGR pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // Stage all but the final pixel as RGBA bytes in the unwritten end of the expanding Vector4 destination. + // The final expanded vector overlaps that staging region, so convert it directly after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + ToUnassociatedVector4(MemoryMarshal.Cast(temporary), destination[..lastIndex]); + destination[lastIndex] = ToUnassociatedVector4(source[lastIndex].R, source[lastIndex].G, source[lastIndex].B, source[lastIndex].A); + } + + /// + /// Converts unassociated scaled vectors to premultiplied ABGR pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied ABGR pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); + } + + /// + /// Converts a floating-point byte magnitude to a byte. + /// + /// The byte magnitude. + /// The rounded and clamped byte. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ConvertToByte(float value) => (byte)Numerics.Clamp(value + .5F, 0, byte.MaxValue); + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index d7cac15309..05799961d8 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -50,15 +50,43 @@ public static void UnsafeFromVector4( PixelConversionModifiers modifiers) where TPixel : unmanaged, IPixel { - ApplyBackwardConversionModifiers(source, modifiers); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) { - UnsafeFromScaledVector4Core(source, destination); + // Transfer functions consume straight color, so preserve the established modifier order before selecting a scalar storage conversion. + ApplyBackwardConversionModifiers(source, modifiers); + + if (scaled) + { + UnsafeFromUnassociatedScaledVector4Core(source, destination); + } + else + { + UnsafeFromUnassociatedVector4Core(source, destination); + } + + return; + } + + if (scaled) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeFromAssociatedScaledVector4Core(source, destination); + } + else + { + UnsafeFromUnassociatedScaledVector4Core(source, destination); + } + } + else if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeFromAssociatedVector4Core(source, destination); } else { - UnsafeFromVector4Core(source, destination); + UnsafeFromUnassociatedVector4Core(source, destination); } } @@ -69,20 +97,82 @@ public static void UnsafeToVector4( PixelConversionModifiers modifiers) where TPixel : unmanaged, IPixel { - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + // Conversion length is defined by the source. Limiting the destination here also keeps modifiers from + // changing spare capacity supplied by callers of either the checked or unchecked conversion path. + destination = destination[..source.Length]; + + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) { - UnsafeToScaledVector4Core(source, destination); + if (scaled) + { + UnsafeToUnassociatedScaledVector4Core(source, destination); + } + else + { + UnsafeToUnassociatedVector4Core(source, destination); + } + + // Transfer functions consume straight color; association is applied only after expansion. + ApplyForwardConversionModifiers(destination, modifiers); + return; + } + + if (scaled) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeToAssociatedScaledVector4Core(source, destination); + } + else + { + UnsafeToUnassociatedScaledVector4Core(source, destination); + } + } + else if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeToAssociatedVector4Core(source, destination); } else { - UnsafeToVector4Core(source, destination); + UnsafeToUnassociatedVector4Core(source, destination); } + } - ApplyForwardConversionModifiers(destination, modifiers); + /// + /// Converts unassociated native-range vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromUnassociatedVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref Vector4 sourceStart = ref MemoryMarshal.GetReference(source); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = TPixel.FromUnassociatedVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } } + /// + /// Converts associated native-range vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromVector4Core( + private static void UnsafeFromAssociatedVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -93,15 +183,46 @@ private static void UnsafeFromVector4Core( while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = TPixel.FromVector4(sourceStart); + destinationBase = TPixel.FromAssociatedVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } + } + + /// + /// Converts pixels to unassociated native-range vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToUnassociatedVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref TPixel sourceStart = ref MemoryMarshal.GetReference(source); + ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = sourceStart.ToUnassociatedVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); } } + /// + /// Converts pixels to associated native-range vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToVector4Core( + private static void UnsafeToAssociatedVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -112,15 +233,21 @@ private static void UnsafeToVector4Core( while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = sourceStart.ToVector4(); + destinationBase = sourceStart.ToAssociatedVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); } } + /// + /// Converts unassociated scaled vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromScaledVector4Core( + private static void UnsafeFromUnassociatedScaledVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -131,15 +258,71 @@ private static void UnsafeFromScaledVector4Core( while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = TPixel.FromScaledVector4(sourceStart); + destinationBase = TPixel.FromUnassociatedScaledVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } + } + + /// + /// Converts associated scaled vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromAssociatedScaledVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref Vector4 sourceStart = ref MemoryMarshal.GetReference(source); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = TPixel.FromAssociatedScaledVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } + } + + /// + /// Converts pixels to unassociated scaled vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToUnassociatedScaledVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref TPixel sourceStart = ref MemoryMarshal.GetReference(source); + ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = sourceStart.ToUnassociatedScaledVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); } } + /// + /// Converts pixels to associated scaled vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToScaledVector4Core( + private static void UnsafeToAssociatedScaledVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -150,7 +333,7 @@ private static void UnsafeToScaledVector4Core( while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = sourceStart.ToScaledVector4(); + destinationBase = sourceStart.ToAssociatedScaledVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 9e649f3c08..886732eb93 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -15,10 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils; internal static partial class Vector4Converters { /// - /// Provides efficient implementations for batched to/from conversion. - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . - /// The method is works by internally converting to a therefore it's not applicable for that type! + /// Provides efficient implementations for batched conversion between RGBA-compatible pixel types and values. /// public static class RgbaCompatible { @@ -51,6 +48,7 @@ internal static void ToVector4( Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); int count = source.Length; + destination = destination[..count]; // Not worth for small buffers: if (count < Vector4ConversionThreshold) @@ -60,14 +58,14 @@ internal static void ToVector4( 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 reducedSource = source[..countWithoutLastItem]; Span lastQuarterOfDestination = MemoryMarshal.Cast(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(lastQuarterOfDestination), MemoryMarshal.Cast(destination[..countWithoutLastItem])); @@ -102,6 +100,10 @@ internal static void FromVector4( int count = source.Length; + // Source defines the conversion length. Do not expose spare destination capacity + // to downstream shuffles whose scalar tail is destination-length driven. + destination = destination[..count]; + // Not worth for small buffers: if (count < Vector4ConversionThreshold) { diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index 0a0b5660d2..d701b0ab5f 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils; internal static partial class Vector4Converters { /// - /// Apply modifiers used requested by ToVector4() conversion. + /// Applies the modifiers requested by ToVector4() conversion. /// /// The span of vectors. /// The modifier rule. @@ -29,7 +29,7 @@ internal static void ApplyForwardConversionModifiers(Span vectors, Pixe } /// - /// Apply modifiers used requested by FromVector4() conversion. + /// Applies the modifiers requested by FromVector4() conversion. /// /// The span of vectors. /// The modifier rule. diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 663d54afc9..7023a3cd98 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -238,9 +238,10 @@ public void Invoke(int y, Span span) Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); targetBuffer.Clear(); - // Execute the bulk pixel format conversion for the current row + // Gamma exposure temporarily stores premultiplied numeric intermediates in TPixel without changing their + // representation. Read the native scaled values back directly to avoid an associate/unassociate round trip. Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span, PixelConversionModifiers.Scale); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); ref ComplexVector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -305,12 +306,14 @@ public int GetRequiredBufferLength(Rectangle bounds) public void Invoke(int y, Span span) { Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); // Input is premultiplied [0,1] so the LUT is safe here. GammaCompanding.Expand(span[..this.bounds.Width], this.gamma); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + // The first convolution pass consumes these values as raw premultiplied numerics. Preserve them in the + // native TPixel representation instead of quantizing through an alpha-association conversion. + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); } } @@ -345,11 +348,13 @@ public void Invoke(int y, Span span) { Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); Numerics.CubePowOnXYZ(span); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + // The first convolution pass consumes these values as raw premultiplied numerics. Preserve them in the + // native TPixel representation instead of quantizing through an alpha-association conversion. + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); } } @@ -389,7 +394,7 @@ public void Invoke(int y) Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, 1F); GammaCompanding.Compress(sourceRowSpan, this.gamma); - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } @@ -427,7 +432,7 @@ public void Invoke(int y) Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index bccf191748..409480871f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -78,6 +78,8 @@ private void Convolve3(int y, Span span) Convolution2DState state = new(in this.kernelMatrixY, in this.kernelMatrixX, this.map); ref int sampleRowBase = ref state.GetSampleRow((uint)(y - this.bounds.Y)); + // Alpha is preserved separately, so filter straight color and restore the source alpha after combining the kernels. + // Clear the target buffers for each row run. targetYBuffer.Clear(); targetXBuffer.Clear(); @@ -92,7 +94,7 @@ private void Convolve3(int y, Span span) // Get the precalculated source sample row for this kernel row and copy to our buffer. int sampleY = Unsafe.Add(ref sampleRowBase, kY); sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -115,7 +117,7 @@ private void Convolve3(int y, Span span) // Now we need to combine the values and copy the original alpha values // from the source row. sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (nuint x = 0; x < (uint)sourceRow.Length; x++) { @@ -128,7 +130,7 @@ private void Convolve3(int y, Span span) } Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -144,6 +146,8 @@ private void Convolve4(int y, Span span) Convolution2DState state = new(in this.kernelMatrixY, in this.kernelMatrixX, this.map); ref int sampleRowBase = ref state.GetSampleRow((uint)(y - this.bounds.Y)); + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffers for each row run. targetYBuffer.Clear(); targetXBuffer.Clear(); @@ -157,9 +161,8 @@ private void Convolve4(int y, Span span) // Get the precalculated source sample row for this kernel row and copy to our buffer. int sampleY = Unsafe.Add(ref sampleRowBase, kY); Span sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); - Numerics.Premultiply(sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); for (uint x = 0; x < (uint)sourceBuffer.Length; x++) @@ -186,11 +189,12 @@ private void Convolve4(int y, Span span) Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - } - Numerics.UnPremultiply(targetYBuffer); + // Independent color and alpha gradients can produce RGB greater than alpha. Restore the associated-vector invariant before either storage representation receives the result. + Numerics.ClampRgbToAlpha(ref target); + } Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 1bbbdb3501..1e94812fd5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -199,12 +199,14 @@ private void Convolve3(int y, Span span) Span sourceBuffer = span[..this.bounds.Width]; Span targetBuffer = span[this.bounds.Width..]; + // Alpha is preserved separately, so filter straight color and restore the source alpha after applying the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -233,9 +235,6 @@ private void Convolve3(int y, Span span) } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - targetStart = ref MemoryMarshal.GetReference(targetBuffer); while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) @@ -247,7 +246,7 @@ private void Convolve3(int y, Span span) } Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -261,14 +260,14 @@ private void Convolve4(int y, Span span) Span sourceBuffer = span[..this.bounds.Width]; Span targetBuffer = span[this.bounds.Width..]; + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -296,10 +295,8 @@ private void Convolve4(int y, Span span) sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, (uint)kernelSize); } - Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } @@ -367,6 +364,8 @@ private void Convolve3(int y, Span span) ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); + // Alpha is preserved separately, so filter straight color and restore the source alpha after applying the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); @@ -380,7 +379,7 @@ private void Convolve3(int y, Span span) // Get the precalculated source sample row for this kernel row and copy to our buffer. sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); @@ -401,7 +400,7 @@ private void Convolve3(int y, Span span) // Now we need to copy the original alpha values from the source row. sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); { ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); @@ -416,7 +415,7 @@ private void Convolve3(int y, Span span) } Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -432,6 +431,8 @@ private void Convolve4(int y, Span span) ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); @@ -445,9 +446,7 @@ private void Convolve4(int y, Span span) // Get the precalculated source sample row for this kernel row and copy to our buffer. sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); @@ -466,10 +465,8 @@ private void Convolve4(int y, Span span) sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); } - Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index feaaf30ce0..e599324f95 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -161,6 +161,8 @@ public void Invoke(int y, Span span) if (this.preserveAlpha) { + // Alpha is preserved separately, so filter straight color and restore the source alpha after applying the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); @@ -171,7 +173,7 @@ public void Invoke(int y, Span span) // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -191,16 +193,20 @@ public void Invoke(int y, Span span) // Now we need to copy the original alpha values from the source row. sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (nuint x = 0; x < (uint)sourceRow.Length; x++) { ref Vector4 target = ref Unsafe.Add(ref targetBase, x); target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } else { + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); @@ -210,9 +216,8 @@ public void Invoke(int y, Span span) // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); - Numerics.Premultiply(sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); for (uint x = 0; x < (uint)sourceBuffer.Length; x++) @@ -229,10 +234,8 @@ public void Invoke(int y, Span span) } } - Numerics.UnPremultiply(targetBuffer); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index eae7481661..5b62b9b8a3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -122,10 +122,12 @@ public void Invoke(int y) for (nuint x = this.minX; x < this.maxX; x++) { - // Grab the max components of the two pixels + // Compass responses are logical color magnitudes. Comparing associated components would make the edge strength + // depend on the storage alpha representation instead of the filtered colors. ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); - currentTargetPixel = TPixel.FromVector4(Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4())); + currentTargetPixel = TPixel.FromUnassociatedScaledVector4( + Vector4.Max(currentPassPixel.ToUnassociatedScaledVector4(), currentTargetPixel.ToUnassociatedScaledVector4())); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs index ca2cabab55..97ddd3be30 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -78,7 +78,7 @@ public void Invoke(int y, Span span) int currentYIndex = Unsafe.Add(ref sampleRowBase, (uint)i); Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth); Span sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } if (this.preserveAlpha) @@ -130,7 +130,7 @@ public void Invoke(int y, Span span) } Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 115db81448..36cc5564a9 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -168,8 +168,8 @@ internal TPixel Dither( return transformed; } - // Calculate the error - Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; + // Palette error is a logical color difference, so it must be diffused independently of the pixel storage representation. + Vector4 error = (source.ToUnassociatedScaledVector4() - transformed.ToUnassociatedScaledVector4()) * scale; int offset = this.offset; DenseMatrix matrix = this.matrix; @@ -200,7 +200,7 @@ internal TPixel Dither( } ref TPixel pixel = ref rowSpan[targetX]; - Vector4 result = pixel.ToVector4(); + Vector4 result = pixel.ToUnassociatedScaledVector4(); // Do not diffuse error into fully transparent pixels. They carry no visible color // (a decoder shows whatever is behind them), so perturbing them is meaningless and, @@ -212,7 +212,7 @@ internal TPixel Dither( } result += error * coefficient; - pixel = TPixel.FromVector4(result); + pixel = TPixel.FromUnassociatedScaledVector4(result); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index f811bae0f7..ddb6d15457 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -119,13 +119,15 @@ public void Invoke(in RowInterval rows) Span redBinSpan = binsSpan[this.levels..]; Span blueBinSpan = redBinSpan[this.levels..]; Span greenBinSpan = blueBinSpan[this.levels..]; + PixelOperations pixelOperations = PixelOperations.Instance; for (int y = rows.Min; y < rows.Max; y++) { Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span, PixelConversionModifiers.Scale); + // Intensity bins and channel averages describe logical colors, so they must not depend on the pixel storage association. + pixelOperations.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = this.bounds.X; x < this.bounds.Right; x++) { @@ -149,7 +151,7 @@ public void Invoke(in RowInterval rows) int offsetX = x + fxr; offsetX = Numerics.Clamp(offsetX, 0, maxX); - Vector4 vector = sourceOffsetRow[offsetX].ToScaledVector4(); + Vector4 vector = sourceOffsetRow[offsetX].ToUnassociatedScaledVector4(); float sourceRed = vector.X; float sourceBlue = vector.Z; @@ -180,7 +182,7 @@ public void Invoke(in RowInterval rows) Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan, PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 37286086c6..8b287b727c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -74,11 +74,14 @@ public int GetRequiredBufferLength(Rectangle bounds) public void Invoke(int y, Span span) { Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + PixelOperations pixelOperations = PixelOperations.Instance; + + // Color matrices transform logical color components, so associated storage must be unassociated before applying the matrix. + pixelOperations.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ColorNumerics.Transform(span, ref Unsafe.AsRef(in this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs index 41560d1200..bffe6cf631 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -56,7 +56,10 @@ public int GetRequiredBufferLength(Rectangle bounds) public void Invoke(int y, Span span) { Span targetRowSpan = this.target.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale); + PixelOperations pixelOperations = PixelOperations.Instance; + + // Opacity changes alpha without changing logical color, so associated storage must be unassociated before replacing alpha. + pixelOperations.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); for (int x = 0; x < this.bounds.Width; x++) @@ -65,7 +68,7 @@ public void Invoke(int y, Span span) v.W = 1F; } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, targetRowSpan[..span.Length], PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 3d54836c5b..b18a52bec2 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -144,8 +144,11 @@ private static void ProcessCornerTile( for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + Vector4 vector = pixel.ToUnassociatedScaledVector4(); + int luminance = ColorNumerics.GetBT709Luminance(vector, luminanceLevels); + float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, luminance); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); } } } @@ -190,8 +193,10 @@ private static void ProcessBorderColumn( for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + Vector4 vector = pixel.ToUnassociatedScaledVector4(); + float luminanceEqualized = InterpolateBetweenTwoTiles(vector, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); } tileY++; @@ -242,8 +247,10 @@ private static void ProcessBorderRow( for (int dx = x; dx < xLimit; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + Vector4 vector = pixel.ToUnassociatedScaledVector4(); + float luminanceEqualized = InterpolateBetweenTwoTiles(vector, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); tileX++; } } @@ -256,7 +263,7 @@ private static void ProcessBorderRow( /// /// Bilinear interpolation between four adjacent tiles. /// - /// The pixel to remap the grey value from. + /// The unassociated scaled vector to remap the grey value from. /// The pre-computed lookup tables to remap the grey values for each tiles. /// The number of tiles in the x-direction. /// The number of tiles in the y-direction. @@ -273,7 +280,7 @@ private static void ProcessBorderRow( /// A re-mapped grey value. [MethodImpl(InliningOptions.ShortMethod)] private static float InterpolateBetweenFourTiles( - TPixel sourcePixel, + Vector4 sourceVector, CdfTileData cdfData, int tileCountX, int tileCountY, @@ -285,7 +292,7 @@ private static float InterpolateBetweenFourTiles( int tileHeight, int luminanceLevels) { - int luminance = GetLuminance(sourcePixel, luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(sourceVector, luminanceLevels); float tx = tileX / (float)(tileWidth - 1); float ty = tileY / (float)(tileHeight - 1); @@ -304,7 +311,7 @@ private static float InterpolateBetweenFourTiles( /// /// Linear interpolation between two tiles. /// - /// The pixel to remap the grey value from. + /// The unassociated scaled vector to remap the grey value from. /// The CDF lookup map. /// X position inside the first tile. /// Y position inside the first tile. @@ -319,7 +326,7 @@ private static float InterpolateBetweenFourTiles( /// A re-mapped grey value. [MethodImpl(InliningOptions.ShortMethod)] private static float InterpolateBetweenTwoTiles( - TPixel sourcePixel, + Vector4 sourceVector, CdfTileData cdfData, int tileX1, int tileY1, @@ -329,7 +336,7 @@ private static float InterpolateBetweenTwoTiles( int tileWidth, int luminanceLevels) { - int luminance = GetLuminance(sourcePixel, luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(sourceVector, luminanceLevels); float tx = tilePos / (float)(tileWidth - 1); float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); @@ -419,8 +426,9 @@ public void Invoke(in RowInterval rows) for (int dx = x; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; + Vector4 vector = pixel.ToUnassociatedScaledVector4(); float luminanceEqualized = InterpolateBetweenFourTiles( - pixel, + vector, this.cdfData, this.tileCount, this.tileCount, @@ -432,7 +440,8 @@ public void Invoke(in RowInterval rows) this.tileHeight, this.luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); tileX++; } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index 8ca4a86a28..624820d2f2 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -194,7 +194,7 @@ private static void CopyPixelRow( int idx = 0; for (int dx = x; dx < x + tileWidth; dx++) { - rowPixels[idx] = source[Numerics.Abs(dx), y].ToVector4(); + rowPixels[idx] = source[Numerics.Abs(dx), y].ToUnassociatedScaledVector4(); idx++; } @@ -209,11 +209,11 @@ private static void CopyPixelRow( if (dx >= source.Width) { int diff = dx - source.Width; - rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); + rowPixels[idx] = source[dx - diff - 1, y].ToUnassociatedScaledVector4(); } else { - rowPixels[idx] = source[dx, y].ToVector4(); + rowPixels[idx] = source[dx, y].ToUnassociatedScaledVector4(); } idx++; @@ -242,7 +242,7 @@ private static void CopyPixelRowFast( int y, int tileWidth, Configuration configuration) - => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); /// /// Adds a column of grey values to the histogram. @@ -256,7 +256,7 @@ private static void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int his { for (nuint idx = 0; idx < (uint)length; idx++) { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); Unsafe.Add(ref histogramBase, (uint)luminance)++; } } @@ -273,7 +273,7 @@ private static void RemovePixelsFromHistogram(ref Vector4 greyValuesBase, ref in { for (nuint idx = 0; idx < (uint)length; idx++) { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); Unsafe.Add(ref histogramBase, (uint)luminance)--; } } @@ -381,9 +381,11 @@ public void Invoke(int x) float numberOfPixelsMinusCdfMin = this.swInfos.PixelInTile - cdfMin; // Map the current pixel to the new equalized value. - int luminance = GetLuminance(this.source[x, y], this.processor.LuminanceLevels); + Vector4 vector = this.source[x, y].ToUnassociatedScaledVector4(); + int luminance = ColorNumerics.GetBT709Luminance(vector, this.processor.LuminanceLevels); float luminanceEqualized = Unsafe.Add(ref cdfBase, (uint)luminance) / numberOfPixelsMinusCdfMin; - this.targetPixels[x, y] = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, this.source[x, y].ToVector4().W)); + vector.X = vector.Y = vector.Z = luminanceEqualized; + this.targetPixels[x, y] = TPixel.FromUnassociatedScaledVector4(vector); // Remove top most row from the histogram, mirroring rows which exceeds the borders. if (this.useFastPath) diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs index e830cc55fc..9acca6d409 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs @@ -142,18 +142,19 @@ public void Invoke(int y, Span span) float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; Span pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + PixelOperations operations = PixelOperations.Instance; + operations.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + int luminance = ColorNumerics.GetBT709Luminance(vector, levels); float scaledLuminance = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; float scalingFactor = scaledLuminance * levels / luminance; Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(scalingFactor * vector.X, scalingFactor * vector.Y, scalingFactor * vector.Z, vector.W); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); + operations.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } @@ -201,12 +202,17 @@ public void Invoke(int y, Span span) int levelsMinusOne = this.luminanceLevels - 1; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - Span pixelRow = sourceAccess.GetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + Span pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations operations = PixelOperations.Instance; + operations.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { - Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x) * levelsMinusOne; + Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); + float alpha = vector.W; + + // Alpha is not a histogram component and must not be scaled into the CDF index range. + vector *= levelsMinusOne; uint originalX = (uint)MathF.Round(vector.X); float scaledX = Unsafe.Add(ref cdfBase, originalX) / noOfPixelsMinusCdfMin; @@ -214,10 +220,10 @@ public void Invoke(int y, Span span) float scaledY = Unsafe.Add(ref cdfBase, originalY) / noOfPixelsMinusCdfMin; uint originalZ = (uint)MathF.Round(vector.Z); float scaledZ = Unsafe.Add(ref cdfBase, originalZ) / noOfPixelsMinusCdfMin; - Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(scaledX, scaledY, scaledZ, vector.W); + Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(scaledX, scaledY, scaledZ, alpha); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); + operations.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 3ab8f7431e..3c07d05c6b 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -124,18 +124,19 @@ public void Invoke(int y, Span span) int levels = this.luminanceLevels; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - Span pixelRow = this.source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + Span pixelRow = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations operations = PixelOperations.Instance; + operations.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + int luminance = ColorNumerics.GetBT709Luminance(vector, levels); float luminanceEqualized = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); + operations.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs index 3584f2954a..c7e40b420f 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs @@ -51,13 +51,13 @@ public void Invoke(int y, Span span) ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); int levels = this.luminanceLevels; - Span pixelRow = this.source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + Span pixelRow = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + int luminance = ColorNumerics.GetBT709Luminance(vector, levels); Interlocked.Increment(ref Unsafe.Add(ref histogramBase, (uint)luminance)); } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index 6ac36f3ce0..fd01bd8dda 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -142,7 +142,8 @@ public void ClipHistogram(Span histogram, int clipLimit) public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) { // TODO: We need a bulk per span equivalent. - Vector4 vector = sourcePixel.ToVector4(); - return ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + // Histogram bins describe logical color, so storage alpha association must not scale luminance. + Vector4 vector = sourcePixel.ToUnassociatedScaledVector4(); + return ColorNumerics.GetBT709Luminance(vector, luminanceLevels); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs index 71c1b84571..d669f885a4 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs @@ -189,8 +189,8 @@ internal sealed class Hexadecatree : IDisposable // Maximum significant bits. private readonly int maxColorBits; - // The threshold for transparent colors. - private readonly int transparencyThreshold255; + // Keep the fractional byte-domain threshold so values such as .5 remain distinct from alpha 127. + private readonly float transparencyThreshold255; // Instead of a reference to the root, we store the index of the root node. // Index 0 is reserved for the root. @@ -221,7 +221,7 @@ public Hexadecatree( { this.maxColorBits = maxColorBits; this.maxColors = maxColors; - this.transparencyThreshold255 = (int)(transparencyThreshold * 255F); + this.transparencyThreshold255 = transparencyThreshold * 255F; this.Leaves = 0; this.previousNode = -1; this.previousColor = default; diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index e121aff90b..1e3453294f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -39,7 +39,7 @@ public static QuantizerOptions DeepClone(this QuantizerOptions options, ActionReturns true if transparent pixels can be replaced; otherwise, false. public static bool ShouldReplacePixelsByAlphaThreshold(float threshold) where TPixel : unmanaged, IPixel - => threshold > 0 && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; + => threshold > 0 && TPixel.GetPixelTypeInfo().AlphaRepresentation != PixelAlphaRepresentation.None; /// /// Replaces pixels in a span with fully transparent pixels based on an alpha threshold. @@ -295,11 +295,11 @@ internal static void AddPaletteColors sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplacePixelsByAlphaThreshold(vectorRow, threshold); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); rowDelegate.Invoke(delegateRow, y); } } @@ -308,11 +308,11 @@ internal static void AddPaletteColors sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); EncodingUtilities.ReplaceTransparentPixels(vectorRow); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); rowDelegate.Invoke(delegateRow, y); } } @@ -363,11 +363,11 @@ private static void SecondPass( for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplacePixelsByAlphaThreshold(vectorRow, threshold); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); for (int x = 0; x < destinationRow.Length; x++) @@ -381,11 +381,11 @@ private static void SecondPass( for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); EncodingUtilities.ReplaceTransparentPixels(vectorRow); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); for (int x = 0; x < destinationRow.Length; x++) @@ -420,11 +420,11 @@ private static void SecondPass( for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplacePixelsByAlphaThreshold(vectorRow, threshold); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } else @@ -432,11 +432,11 @@ private static void SecondPass( for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); EncodingUtilities.ReplaceTransparentPixels(vectorRow); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 03d6ac0da6..b4b95adc1b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -144,7 +144,8 @@ private void ResolvePalette() normalized = Vector4.Zero; } - paletteSpan[k] = TPixel.FromScaledVector4(normalized); + // Histogram moments are accumulated from straight-alpha Rgba32 values, so the normalized palette color is unassociated. + paletteSpan[k] = TPixel.FromUnassociatedScaledVector4(normalized); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs index 8a76ca42f3..b9973eea35 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -96,13 +96,16 @@ private static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, int height = bitmap.Height; Point topLeft = default; Point bottomRight = default; + + // BinaryThresholdProcessor produces opaque black and white, so association no longer affects the components. Scaled vectors still normalize pixel formats whose native vectors use another range. Func, int, int, float, bool> delegateFunc = channel switch { - RgbaComponent.R => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon, - RgbaComponent.G => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon, - RgbaComponent.B => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon, - _ => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon, + RgbaComponent.R => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().X - b) > Constants.Epsilon, + RgbaComponent.G => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().Y - b) > Constants.Epsilon, + RgbaComponent.B => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().Z - b) > Constants.Epsilon, + _ => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().W - b) > Constants.Epsilon, }; + int GetMinY(ImageFrame pixels) { for (int y = 0; y < height; y++) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 260a366b92..61cde6e2a7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -199,15 +199,14 @@ public void Invoke(in RowInterval rows, Span span) int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; int maxX = this.bounds.Right - 1; + PixelOperations pixelOperations = PixelOperations.Instance; for (int y = rows.Min; y < rows.Max; y++) { Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - destinationRowSpan, - span, - PixelConversionModifiers.Scale); + + // The temporary row is reused, so clear values for out-of-bounds samples that skip the resampling loop below. + span.Clear(); for (int x = 0; x < span.Length; x++) { @@ -225,6 +224,7 @@ public void Invoke(in RowInterval rows, Span span) continue; } + // Resampling interpolates color with coverage, so use associated vectors to prevent transparent RGB from bleeding into visible edges. Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { @@ -235,8 +235,7 @@ public void Invoke(in RowInterval rows, Span span) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceRowSpan[xK].ToScaledVector4(); - Numerics.Premultiply(ref current); + Vector4 current = sourceRowSpan[xK].ToAssociatedScaledVector4(); sum += current * xWeight * yWeight; } } @@ -244,12 +243,7 @@ public void Invoke(in RowInterval rows, Span span) span[x] = sum; } - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - destinationRowSpan, - PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, destinationRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index ecf587684d..90ca8743c0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -197,15 +197,14 @@ public void Invoke(in RowInterval rows, Span span) int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; int maxX = this.bounds.Right - 1; + PixelOperations pixelOperations = PixelOperations.Instance; for (int y = rows.Min; y < rows.Max; y++) { Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - destinationRowSpan, - span, - PixelConversionModifiers.Scale); + + // The temporary row is reused, so clear values for out-of-bounds samples that skip the resampling loop below. + span.Clear(); for (int x = 0; x < span.Length; x++) { @@ -223,6 +222,7 @@ public void Invoke(in RowInterval rows, Span span) continue; } + // Resampling interpolates color with coverage, so use associated vectors to prevent transparent RGB from bleeding into visible edges. Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { @@ -233,8 +233,7 @@ public void Invoke(in RowInterval rows, Span span) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceRowSpan[xK].ToScaledVector4(); - Numerics.Premultiply(ref current); + Vector4 current = sourceRowSpan[xK].ToAssociatedScaledVector4(); sum += current * xWeight * yWeight; } } @@ -242,12 +241,7 @@ public void Invoke(in RowInterval rows, Span span) span[x] = sum; } - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - destinationRowSpan, - PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, destinationRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index a85487d1c1..c0380224c4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -104,8 +104,8 @@ public Vector4 ConvolveCore(ref Vector4 rowStartRef) Vector256 pixels256_0 = Unsafe.As>(ref rowStartRef); Vector256 pixels256_1 = Unsafe.As>(ref Unsafe.Add(ref rowStartRef, (nuint)2)); - result256_0 = Vector256_.MultiplyAdd(result256_0, Vector256.Load(bufferStart), pixels256_0); - result256_1 = Vector256_.MultiplyAdd(result256_1, Vector256.Load(bufferStart + 8), pixels256_1); + result256_0 = Vector256_.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0); + result256_1 = Vector256_.MultiplyAddEstimate(Vector256.Load(bufferStart + 8), pixels256_1, result256_1); bufferStart += 16; rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)4); @@ -116,7 +116,7 @@ public Vector4 ConvolveCore(ref Vector4 rowStartRef) if ((this.Length & 3) >= 2) { Vector256 pixels256_0 = Unsafe.As>(ref rowStartRef); - result256_0 = Vector256_.MultiplyAdd(result256_0, Vector256.Load(bufferStart), pixels256_0); + result256_0 = Vector256_.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0); bufferStart += 8; rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)2); @@ -127,7 +127,7 @@ public Vector4 ConvolveCore(ref Vector4 rowStartRef) if ((this.Length & 1) != 0) { Vector128 pixels128 = Unsafe.As>(ref rowStartRef); - result128 = Vector128_.MultiplyAdd(result128, Vector128.Load(bufferStart), pixels128); + result128 = Vector128_.MultiplyAddEstimate(Vector128.Load(bufferStart), pixels128, result128); } return result128.AsVector4(); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 48f4a746f0..870c382413 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -189,16 +189,6 @@ private static void ApplyNNResizeFrameTransform( in operation); } - private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) - { - if (premultiplyAlpha) - { - return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); - } - - return PixelConversionModifiers.None.ApplyCompanding(compand); - } - private static void ApplyResizeFrameTransform( Configuration configuration, ImageFrame source, @@ -211,12 +201,9 @@ private static void ApplyResizeFrameTransform( bool compand, bool premultiplyAlpha) { - PixelAlphaRepresentation? alphaRepresentation = PixelOperations.Instance.GetPixelTypeInfo().AlphaRepresentation; - - // Premultiply only if alpha representation is unknown or Unassociated: - bool needsPremultiplication = alphaRepresentation == null || alphaRepresentation.Value == PixelAlphaRepresentation.Unassociated; - premultiplyAlpha &= needsPremultiplication; - PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); + // Resampling uses normalized vectors and interpolates them in the alpha representation selected by the processor options. + PixelConversionModifiers alphaModifier = premultiplyAlpha ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.UnPremultiply; + PixelConversionModifiers conversionModifiers = (PixelConversionModifiers.Scale | alphaModifier).ApplyCompanding(compand); Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index a0a8c2173e..599882fdb8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -169,7 +169,6 @@ public void FillDestinationPixels(RowInterval rowInterval, Buffer2D dest } Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } } @@ -215,12 +214,7 @@ private void CalculateFirstPassValues(RowInterval calculationInterval) for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToVector4( - this.configuration, - sourceRow, - tempRowSpan, - this.conversionModifiers); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, tempRowSpan, this.conversionModifiers); ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - currentWindowMin]; diff --git a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs index 80b0963440..dcd26c328d 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs @@ -14,7 +14,6 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Bulk; -[Config(typeof(Config.Short))] public abstract class FromVector4 where TPixel : unmanaged, IPixel { @@ -62,14 +61,21 @@ public void PixelOperations_Specialized() => PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.Source.GetSpan(), this.Destination.GetSpan()); } +[Config(typeof(Config.Short))] public class FromVector4Rgba32 : FromVector4 { + /// + /// Measures the raw SIMD kernel that converts normalized RGBA vector components to bytes. + /// [Benchmark] public void UseHwIntrinsics() { Span sBytes = MemoryMarshal.Cast(this.Source.GetSpan()); Span dFloats = MemoryMarshal.Cast(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 void UseAvx2_Grouped() } /* - 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 | - | + */ +} + +/// +/// Measures bulk conversion from values to premultiplied pixels. +/// +[Config(typeof(Config.Analysis))] +public class FromVector4Bgra32P : FromVector4 +{ + /* + 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 | 53.35 ns | 0.208 ns | 0.311 ns | 1.00 | 1,152 B | - | + | PixelOperations_Specialized | 64 | 46.61 ns | 0.195 ns | 0.280 ns | 0.87 | 2,975 B | - | + | PixelOperations_Base | 256 | 212.11 ns | 0.719 ns | 1.076 ns | 1.00 | 1,152 B | - | + | PixelOperations_Specialized | 256 | 77.12 ns | 0.824 ns | 1.233 ns | 0.36 | 2,992 B | - | + | PixelOperations_Base | 2048 | 1,435.87 ns | 9.402 ns | 13.781 ns | 1.00 | 1,152 B | - | + | PixelOperations_Specialized | 2048 | 250.89 ns | 9.354 ns | 13.416 ns | 0.17 | 2,992 B | - | */ } diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs index 6499632b69..38a09483bf 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs @@ -20,24 +20,48 @@ public void PixelOperations_Base() 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 | - | +} + +/// +/// Measures bulk conversion from premultiplied pixels to values. +/// +[Config(typeof(Config.Analysis))] +public class ToVector4_Bgra32P : ToVector4 +{ + /// + /// Measures the scalar implementation used as the comparison baseline. + /// + [Benchmark(Baseline = true)] + public void PixelOperations_Base() + { + new PixelOperations().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 | RatioSD | Code Size | Allocated | + // |---------------------------- |------ |------------:|---------:|---------:|------:|--------:|----------:|----------:| + // | PixelOperations_Base | 64 | 58.05 ns | 3.204 ns | 4.795 ns | 1.01 | 0.11 | 932 B | - | + // | PixelOperations_Specialized | 64 | 84.78 ns | 0.467 ns | 0.669 ns | 1.47 | 0.12 | 3,731 B | - | + // | PixelOperations_Base | 256 | 208.93 ns | 0.345 ns | 0.484 ns | 1.00 | 0.00 | 958 B | - | + // | PixelOperations_Specialized | 256 | 106.50 ns | 0.280 ns | 0.410 ns | 0.51 | 0.00 | 3,738 B | - | + // | PixelOperations_Base | 2048 | 1,617.39 ns | 3.380 ns | 4.848 ns | 1.00 | 0.00 | 958 B | - | + // | PixelOperations_Specialized | 2048 | 269.90 ns | 0.666 ns | 0.976 ns | 0.17 | 0.00 | 3,735 B | - | } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 9231fc8b4c..0c46de4707 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -8,6 +8,7 @@ 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 Short() => this.AddJob( .WithArguments([new MsBuildArgument("/p:DebugType=portable")])); } + /// + /// Configures a short diagnostic run that exports memory usage, generated code, and full measurement data. + /// Elevated Windows runs also include branch-instruction counters. + /// + 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( diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs new file mode 100644 index 0000000000..02f25f2ea3 --- /dev/null +++ b/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; + +/// +/// Compares straight-alpha and associated-alpha bulk pixel blending. +/// +[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 unassociatedBlender; + private PixelBlender associatedBlender; + + /// + /// Gets or sets the color-blending mode measured by the benchmark. + /// + [ParamsAllValues] + public PixelColorBlendingMode ColorMode { get; set; } + + /// + /// Gets or sets the alpha-composition mode measured by the benchmark. + /// + [ParamsAllValues] + public PixelAlphaCompositionMode AlphaMode { get; set; } + + /// + /// Initializes equivalent straight-alpha and associated-alpha rows. + /// + [GlobalSetup] + public void Setup() + { + this.unassociatedBlender = PixelOperations.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode); + this.associatedBlender = PixelOperations.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(); + } + } + + /// + /// Blends one row stored with straight alpha. + /// + /// The last destination pixel. + [Benchmark(Description = "Straight alpha", Baseline = true)] + public Rgba32 BlendUnassociated() + { + this.unassociatedBlender.Blend( + Configuration.Default, + this.unassociatedDestination, + this.unassociatedBackground, + this.unassociatedSource, + this.amounts, + this.unassociatedWorkingBuffer); + + return this.unassociatedDestination[^1]; + } + + /// + /// Blends one row stored with associated alpha. + /// + /// The last destination pixel. + [Benchmark(Description = "Associated alpha")] + public Rgba32P BlendAssociated() + { + this.associatedBlender.Blend( + Configuration.Default, + this.associatedDestination, + this.associatedBackground, + this.associatedSource, + this.amounts, + this.associatedWorkingBuffer); + + return this.associatedDestination[^1]; + } +} diff --git a/tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj b/tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj new file mode 100644 index 0000000000..33bbc1b940 --- /dev/null +++ b/tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj @@ -0,0 +1,23 @@ + + + + + SixLabors.ImageSharp.PublicApi.Tests + SixLabors.ImageSharp.PublicApi.Tests + Debug;Release + + + + + + net8.0;net10.0 + + + + + net8.0 + + + + + diff --git a/tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs b/tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs new file mode 100644 index 0000000000..a1916831ff --- /dev/null +++ b/tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.PublicApi.Tests; + +/// +/// Verifies that representation-aware pixel operations can be implemented outside the ImageSharp assembly. +/// +public class PixelOperationsExtensibilityTests +{ + /// + /// Verifies that an external subclass can override and invoke every representation-aware hook. + /// + [Fact] + public void PixelOperationsExposesAllRepresentationHooksToExternalSubclasses() + { + ExternalRgba32PixelOperations operations = new(); + + operations.InvokeRepresentationHooks(); + + Assert.Equal(RepresentationHooks.All, operations.InvokedHooks); + } + + /// + /// Verifies that an external subclass can override and invoke every representation-aware hook. + /// + [Fact] + public void AssociatedAlphaPixelOperationsExposesAllRepresentationHooksToExternalSubclasses() + { + ExternalRgba32PPixelOperations operations = new(); + + operations.InvokeRepresentationHooks(); + + Assert.Equal(RepresentationHooks.All, operations.InvokedHooks); + } + + [Flags] + private enum RepresentationHooks + { + None = 0, + ToUnassociatedVector4 = 1 << 0, + ToAssociatedVector4 = 1 << 1, + ToUnassociatedScaledVector4 = 1 << 2, + ToAssociatedScaledVector4 = 1 << 3, + FromUnassociatedVector4Destructive = 1 << 4, + FromAssociatedVector4Destructive = 1 << 5, + FromUnassociatedScaledVector4Destructive = 1 << 6, + FromAssociatedScaledVector4Destructive = 1 << 7, + All = ToUnassociatedVector4 + | ToAssociatedVector4 + | ToUnassociatedScaledVector4 + | ToAssociatedScaledVector4 + | FromUnassociatedVector4Destructive + | FromAssociatedVector4Destructive + | FromUnassociatedScaledVector4Destructive + | FromAssociatedScaledVector4Destructive + } + + /// + /// Exercises the representation-aware hooks inherited directly from . + /// + private sealed class ExternalRgba32PixelOperations : PixelOperations + { + /// + /// Gets the hooks invoked through this external subclass. + /// + public RepresentationHooks InvokedHooks { get; private set; } + + /// + /// Invokes every representation-aware operation exposed to derived classes. + /// + public void InvokeRepresentationHooks() + { + // Empty spans isolate accessibility and virtual dispatch; conversion behavior is covered by the pixel-operation test suite. + this.ToUnassociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToUnassociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.FromUnassociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromUnassociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + } + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedVector4; + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedVector4; + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedScaledVector4; + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedScaledVector4; + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedVector4Destructive; + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedVector4Destructive; + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedScaledVector4Destructive; + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedScaledVector4Destructive; + } + + /// + /// Exercises the representation-aware hooks inherited through . + /// + private sealed class ExternalRgba32PPixelOperations : AssociatedAlphaPixelOperations + { + /// + /// Gets the hooks invoked through this external subclass. + /// + public RepresentationHooks InvokedHooks { get; private set; } + + /// + /// Invokes every representation-aware operation exposed to derived classes. + /// + public void InvokeRepresentationHooks() + { + // Empty spans isolate accessibility and virtual dispatch; conversion behavior is covered by the pixel-operation test suite. + this.ToUnassociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToUnassociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.FromUnassociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromUnassociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + } + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedVector4; + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedVector4; + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedScaledVector4; + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedScaledVector4; + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedVector4Destructive; + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedVector4Destructive; + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedScaledVector4Destructive; + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedScaledVector4Destructive; + } +} diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 2eb821b61d..8897464539 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -35,6 +35,20 @@ public void Rgba32() 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()); + } + [Fact] public void Argb32() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index 2e2f0d07db..66537007fb 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -114,6 +114,65 @@ public void Vector4Constructor() Assert.Equal(new L8(255), color.ToPixel()); } + [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()); + Assert.Equal(new Rgba32(128, 64, 32, 128), color.ToPixel()); + } + + [Fact] + public void UnassociatedPixelToAssociatedPixel() + { + Color color = Color.FromPixel(new Rgba32(128, 64, 32, 128)); + + // Act: + Rgba32P actual = color.ToPixel(); + + // 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()); + } + + [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(source, colors); + Color.ToPixel(colors, destination); + + // Assert: + Assert.Equal(source, destination); + Assert.All(colors, color => Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation)); + } + [Fact] public void GenericPixelRoundTrip() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index c482fc9986..6c205888af 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests; @@ -18,6 +19,58 @@ public void WithAlpha() Assert.Equal(expected, c2.ToPixel()); } + [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()); + Assert.Equal(new Rgba32(128, 64, 32, 64), actual.ToPixel()); + } + + [Fact] + public void ToHexConvertsAssociatedRepresentation() + { + Color color = Color.FromPixel(new Rgba32P(64, 32, 16, 128)); + + Assert.Equal("80402080", color.ToHex()); + } + + [Fact] + public void ToScaledVector4ConvertsUnassociatedToAssociated() + { + Color color = Color.FromScaledVector(new Vector4(1F, 0.5F, 0.25F, 0.5F)); + + Vector4 actual = color.ToScaledVector4(PixelAlphaRepresentation.Associated); + + Assert.Equal(new Vector4(0.5F, 0.25F, 0.125F, 0.5F), actual); + } + + [Fact] + public void ToScaledVector4ConvertsAssociatedToUnassociated() + { + Color color = Color.FromScaledVector(new Vector4(0.5F, 0.25F, 0.125F, 0.5F), PixelAlphaRepresentation.Associated); + + Vector4 actual = color.ToScaledVector4(PixelAlphaRepresentation.Unassociated); + + Assert.Equal(new Vector4(1F, 0.5F, 0.25F, 0.5F), actual); + } + + [Fact] + public void EqualityDoesNotDependOnInternalAssociationStorage() + { + Color fromPixel = Color.FromPixel(new Rgba32P(64, 32, 16, 128)); + Color fromVector = Color.FromScaledVector(fromPixel.ToScaledVector4(), PixelAlphaRepresentation.Associated); + + Assert.Equal(fromPixel, fromVector); + Assert.Equal(fromPixel.GetHashCode(), fromVector.GetHashCode()); + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/tests/ImageSharp.Tests/Color/RgbaDouble.cs b/tests/ImageSharp.Tests/Color/RgbaDouble.cs index 76fdf365c4..2487d5fdef 100644 --- a/tests/ImageSharp.Tests/Color/RgbaDouble.cs +++ b/tests/ImageSharp.Tests/Color/RgbaDouble.cs @@ -106,6 +106,47 @@ public static PixelTypeInfo GetPixelTypeInfo() /// public static PixelOperations CreatePixelOperations() => new(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RgbaDouble FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index dfd0a6b307..71335032c8 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -3,8 +3,10 @@ using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit.Abstractions; @@ -133,7 +135,7 @@ static void RunTest(string serialized) => TestImpl_BulkConvertByteToNormalizedFl FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX); } [Theory] @@ -142,17 +144,41 @@ public void BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertB count, (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); + [Fact] + public void VectorMultiplyAddUsesRuntimeOrderAndFusedContract() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunVectorMultiplyAddUsesRuntimeOrderAndFusedContract, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void RunVectorMultiplyAddUsesRuntimeOrderAndFusedContract() + { + Assert.Equal(Vector128.Create(11F), Vector128_.MultiplyAddEstimate(Vector128.Create(2F), Vector128.Create(3F), Vector128.Create(5F))); + Assert.Equal(Vector256.Create(11F), Vector256_.MultiplyAddEstimate(Vector256.Create(2F), Vector256.Create(3F), Vector256.Create(5F))); + Assert.Equal(Vector512.Create(11F), Vector512_.MultiplyAddEstimate(Vector512.Create(2F), Vector512.Create(3F), Vector512.Create(5F))); + + // These associated-alpha components produce an exact midpoint that a separate multiply and add rounds incorrectly. + float left = (68F / byte.MaxValue) * .625F; + float right = 1F - (160F / byte.MaxValue); + float addend = ((50F / byte.MaxValue) + (50F / byte.MaxValue)) * left; + float expected = MathF.FusedMultiplyAdd(left, right, addend); + + Assert.Equal(Vector128.Create(expected), Vector128_.FusedMultiplyAdd(Vector128.Create(left), Vector128.Create(right), Vector128.Create(addend))); + Assert.Equal(Vector256.Create(expected), Vector256_.FusedMultiplyAdd(Vector256.Create(left), Vector256.Create(right), Vector256.Create(addend))); + Assert.Equal(Vector512.Create(expected), Vector512_.FusedMultiplyAdd(Vector512.Create(left), Vector512.Create(right), Vector512.Create(addend))); + } + private static void TestImpl_BulkConvertByteToNormalizedFloat( int count, Action, Memory> convert) { - byte[] source = new Random(count).GenerateRandomByteArray(count); + byte[] source = Enumerable.Range(0, count).Select(i => (byte)i).ToArray(); float[] result = new float[count]; - float[] expected = source.Select(b => b / 255f).ToArray(); + // A double-precision oracle keeps this expectation independent from either production implementation. + float[] expected = source.Select(b => (float)(b / (double)byte.MaxValue)).ToArray(); convert(source, result); - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(expected, result); } [Theory] @@ -193,6 +219,45 @@ public void BulkConvertNormalizedFloatToByteClampOverflows(int count) } } + [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( diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs index 7b7917dbc4..cb527cf3b2 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs @@ -72,6 +72,17 @@ public void Identify_DetectsCorrectImageDataType(string imagePath, ExrImageDataT Assert.Equal(expectedImageDataType, metadata.ImageDataType); } + [Theory] + [InlineData(ExrImageDataType.Rgba, PixelAlphaRepresentation.Associated)] + [InlineData(ExrImageDataType.Rgb, PixelAlphaRepresentation.None)] + [InlineData(ExrImageDataType.Gray, PixelAlphaRepresentation.None)] + public void GetPixelTypeInfo_ReturnsExpectedAlphaRepresentation(ExrImageDataType imageDataType, PixelAlphaRepresentation expected) + { + ExrMetadata metadata = new() { ImageDataType = imageDataType }; + + Assert.Equal(expected, metadata.GetPixelTypeInfo().AlphaRepresentation); + } + [Theory] [InlineData(TestImages.Exr.UncompressedRgba, ExrCompression.None)] [InlineData(TestImages.Exr.B44, ExrCompression.B44)] diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index f895afbd51..c41f455cd1 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -120,12 +119,6 @@ public void Encode_WithTransparentColorBehaviorClear_Works() for (int x = 0; x < accessor.Width; x++) { - if (expectedColor != rowSpan[x]) - { - int xx = 0; - } - - Assert.Equal(expectedColor, rowSpan[x]); } } diff --git a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs index 31ec27da0c..5ec23b1464 100644 --- a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Qoi; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs new file mode 100644 index 0000000000..6a0afcd859 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; + +[Trait("Format", "Tiff")] +public class RgbaTiffColorTests : PhotometricInterpretationTestBase +{ + [Fact] + public void DecodeUsesAlphaChannelBitDepth() + { + // The single pixel packs 4-bit RGB followed by 8-bit alpha. Using the blue-channel width for alpha would consume + // only the first alpha nibble and leave the decoded opacity at 8 instead of 128. + byte[] input = [0xF0, 0x88, 0x00]; + Rgba32[][] expected = [[new Rgba32(255, 0, 136, 128)]]; + + AssertDecode(expected, pixels => + { + RgbaTiffColor color = new(TiffExtraSampleType.UnassociatedAlphaData, new TiffBitsPerSample(4, 4, 4, 8)); + color.Decode(input, pixels, 0, 0, 1, 1); + }); + } + + [Fact] + public void DecodeAssociatedAlphaRestoresLogicalColor() + { + byte[] input = [64, 32, 16, 128]; + Rgba32[][] expected = [[new Rgba32(128, 64, 32, 128)]]; + + AssertDecode(expected, pixels => + { + RgbaTiffColor color = new(TiffExtraSampleType.AssociatedAlphaData, new TiffBitsPerSample(8, 8, 8, 8)); + color.Decode(input, pixels, 0, 0, 1, 1); + }); + } + + [Fact] + public void DecodeUnassociatedAlphaAssociatesDestinationStorage() + { + byte[] input = [128, 64, 32, 128]; + using Image image = new(1, 1); + + RgbaTiffColor color = new(TiffExtraSampleType.UnassociatedAlphaData, new TiffBitsPerSample(8, 8, 8, 8)); + color.Decode(input, image.Frames.RootFrame.PixelBuffer, 0, 0, 1, 1); + + // The decoder receives straight TIFF channels, while Rgba32P stores RGB multiplied by its quantized alpha byte. + Assert.Equal(new Rgba32P(64, 32, 16, 128), image[0, 0]); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index f98363b7e6..de893aca68 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -4,6 +4,7 @@ // ReSharper disable InconsistentNaming using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -390,7 +391,15 @@ public void TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha(TestImagePro where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(TiffDecoder.Instance); - image.DebugSave(provider); + PngEncoder encoder = new() + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.RgbWithAlpha + }; + + // This exact Rgba64 comparison requires a 16-bit reference. The Windows reference encoder uses a 32-bit + // System.Drawing bitmap and would otherwise quantize each channel to 8 bits while producing the PNG. + image.DebugSave(provider, testOutputDetails: null, extension: "png", encoder: encoder); image.CompareToReferenceOutput(ImageComparer.Exact, provider); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs index 6483b63c52..104ab76a7d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs @@ -20,6 +20,26 @@ public void ColorFromRgba64Premultiplied_WithZeroAlpha_ReturnsDefaultPixel(ushor Assert.Equal(default, actual); } + [Fact] + public void ColorScaleTo24BitPremultiplied_WithZeroAlpha_ReturnsDefaultPixel() + { + Rgba64 unassociated = TiffUtilities.ColorScaleTo24BitPremultiplied(0x123456, 0x654321, 0xABCDEF, 0); + Rgba32P associated = TiffUtilities.ColorScaleTo24BitPremultiplied(0x123456, 0x654321, 0xABCDEF, 0); + + Assert.Equal(default, unassociated); + Assert.Equal(default, associated); + } + + [Fact] + public void ColorScaleTo32BitPremultiplied_WithZeroAlpha_ReturnsDefaultPixel() + { + Rgba64 unassociated = TiffUtilities.ColorScaleTo32BitPremultiplied(0x12345678, 0x654321AB, 0xABCDEF12, 0); + Rgba32P associated = TiffUtilities.ColorScaleTo32BitPremultiplied(0x12345678, 0x654321AB, 0xABCDEF12, 0); + + Assert.Equal(default, unassociated); + Assert.Equal(default, associated); + } + [Theory] [InlineData(65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535)] [InlineData(32767, 0, 0, 65535, 32767, 0, 0, 65535)] @@ -36,9 +56,11 @@ public void ColorFromRgba64Premultiplied_WithNoAlpha_ReturnExpectedValues(ushort [InlineData(32766, 0, 0, 32766, 65535, 0, 0, 32766)] // Red, 50% Alpha [InlineData(0, 32766, 0, 32766, 0, 65535, 0, 32766)] // Green, 50% Alpha [InlineData(0, 0, 32766, 32766, 0, 0, 65535, 32766)] // Blue, 50% Alpha - [InlineData(8191, 0, 0, 16383, 32765, 0, 0, 16383)] // Red, 25% Alpha - [InlineData(0, 8191, 0, 16383, 0, 32765, 0, 16383)] // Green, 25% Alpha - [InlineData(0, 0, 8191, 16383, 0, 0, 32765, 16383)] // Blue, 25% Alpha + /* The exact 25% result lies below the 16-bit midpoint by less than one float ULP. The Vector4 conversion therefore + reaches the midpoint and applies the established round-to-even contract without requiring a comparison tolerance. */ + [InlineData(8191, 0, 0, 16383, 32766, 0, 0, 16383)] // Red, 25% Alpha + [InlineData(0, 8191, 0, 16383, 0, 32766, 0, 16383)] // Green, 25% Alpha + [InlineData(0, 0, 8191, 16383, 0, 0, 32766, 16383)] // Blue, 25% Alpha [InlineData(8191, 0, 0, 0, 0, 0, 0, 0)] // Red, 0% Alpha [InlineData(0, 8191, 0, 0, 0, 0, 0, 0)] // Green, 0% Alpha [InlineData(0, 0, 8191, 0, 0, 0, 0, 0)] // Blue, 0% Alpha diff --git a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs index 4e24b111bc..903e997aad 100644 --- a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs @@ -18,7 +18,7 @@ public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminan Vector4 vector = new(x, y, z, 0.0f); // act - int actual = ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + int actual = ColorNumerics.GetBT709Luminance(vector, luminanceLevels); // assert Assert.Equal(expected, actual); diff --git a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs index 1106144c4f..35109d352f 100644 --- a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs @@ -11,6 +11,11 @@ public class NumericsTests private readonly ApproximateFloatComparer approximateFloatComparer = new(1e-6f); + /// + /// Gets lengths that straddle the Vector4 block boundaries used by the 128-, 256-, and 512-bit kernels. + /// + public static TheoryData AssociationSpanLengths => new() { 0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63 }; + [Theory] [InlineData(0)] [InlineData(1)] @@ -156,14 +161,23 @@ public void LeastCommonMultiple(int a, int b, int expected) } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] + [MemberData(nameof(AssociationSpanLengths))] public void PremultiplyVectorSpan(int length) { Random rnd = new(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + + for (int i = 0; i < source.Length; i++) + { + // Exact zero and one exercise the special alpha boundaries alongside the random fractional values. + source[i].W = i % 5 switch + { + 0 => 0F, + 1 => 1F, + _ => source[i].W + }; + } + Vector4[] expected = source.Select(v => { Numerics.Premultiply(ref v); @@ -173,17 +187,32 @@ public void PremultiplyVectorSpan(int length) Numerics.Premultiply(source); Assert.Equal(expected, source, this.approximateFloatComparer); + + for (int i = 0; i < source.Length; i++) + { + // Alpha is storage metadata here and must survive each SIMD width without any floating-point transformation. + Assert.Equal(BitConverter.SingleToInt32Bits(expected[i].W), BitConverter.SingleToInt32Bits(source[i].W)); + } } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] + [MemberData(nameof(AssociationSpanLengths))] public void UnPremultiplyVectorSpan(int length) { Random rnd = new(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + + for (int i = 0; i < source.Length; i++) + { + // A zero alpha preserves the complete source vector by contract; one also verifies the identity case. + source[i].W = i % 5 switch + { + 0 => 0F, + 1 => 1F, + _ => source[i].W + }; + } + Vector4[] expected = source.Select(v => { Numerics.UnPremultiply(ref v); @@ -193,6 +222,12 @@ public void UnPremultiplyVectorSpan(int length) Numerics.UnPremultiply(source); Assert.Equal(expected, source, this.approximateFloatComparer); + + for (int i = 0; i < source.Length; i++) + { + // Alpha is the divisor and must still survive each SIMD width without any floating-point transformation. + Assert.Equal(BitConverter.SingleToInt32Bits(expected[i].W), BitConverter.SingleToInt32Bits(source[i].W)); + } } [Theory] diff --git a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs new file mode 100644 index 0000000000..c115850fc0 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -0,0 +1,1364 @@ +// 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.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Verifies the shared contract for pixel formats that store associated alpha. +/// +/// The associated-alpha pixel format. +[Trait("Category", "PixelFormats")] +public abstract class AssociatedAlphaPixelTests + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the color channels described by the pixel format. + /// + 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() * 8) / 4; + + Assert.Equal(Unsafe.SizeOf() * 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)); + } + } +} + +/// +/// Tests the pixel format. +/// +public class Rgba32PTests : AssociatedAlphaPixelTests +{ + [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); + } +} + +/// +/// Tests the pixel format. +/// +public class Bgra32PTests : AssociatedAlphaPixelTests +{ + /// + 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); + } +} + +/// +/// Tests the pixel format. +/// +public class Argb32PTests : AssociatedAlphaPixelTests +{ + [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); + } +} + +/// +/// Tests the pixel format. +/// +public class Abgr32PTests : AssociatedAlphaPixelTests +{ + /// + 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); + } +} + +/// +/// Tests the pixel format. +/// +public class NormalizedByte4PTests : AssociatedAlphaPixelTests +{ + [Fact] + public void PackedValueMatchesNormalizedByte4ForAssociatedVector() + { + Vector4 associated = new(.1F, -.3F, .5F, -.7F); + + Assert.Equal(new NormalizedByte4(associated).PackedValue, new NormalizedByte4P(associated).PackedValue); + } + + [Fact] + public void MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedByte4P pixel = new() { PackedValue = 0x80808080 }; + + Assert.Equal(-Vector4.One, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + Assert.Equal(-Vector4.One, pixel.ToUnassociatedVector4()); + Assert.Equal(Vector4.Zero, pixel.ToUnassociatedScaledVector4()); + } + + [Fact] + public void AssociatedScaledVectorsMatchUnassociatedVectorsWithinTwoUlpsForEveryValidComponentAndAlpha() + { + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + uint component = (byte)(associated - 127); + uint alphaComponent = (byte)(alpha - 127); + NormalizedByte4P pixel = new() { PackedValue = component | (component << 8) | (component << 16) | (alphaComponent << 24) }; + Vector4 expected = pixel.ToUnassociatedScaledVector4(); + expected.X *= expected.W; + expected.Y *= expected.W; + expected.Z *= expected.W; + Vector4 actual = pixel.ToAssociatedScaledVector4(); + + // All 32,640 valid storage pairs are covered. Two ULP is the measured maximum from the independently ordered division and multiplication. + AlphaRepresentationTestAssertions.EqualWithinTwoUlps(expected, actual); + Assert.Equal(BitConverter.SingleToInt32Bits(expected.W), BitConverter.SingleToInt32Bits(actual.W)); + } + } + } + + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertBulkConversionsMatchScalar() + { + // Values immediately below, at, and above each vector width force every hardware regime through its vector body and scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + NormalizedByte4P[] pixels = new NormalizedByte4P[length]; + Vector4[] expectedNative = new Vector4[length]; + Vector4[] expectedAssociatedNative = new Vector4[length]; + Vector4[] expectedAssociatedScaled = new Vector4[length]; + Vector4[] expectedUnassociatedNative = new Vector4[length]; + Vector4[] expectedUnassociatedScaled = new Vector4[length]; + Vector4[] actualNative = new Vector4[length]; + Vector4[] actualAssociatedNative = new Vector4[length]; + Vector4[] actualAssociatedScaled = new Vector4[length]; + Vector4[] actualUnassociatedNative = new Vector4[length]; + Vector4[] actualUnassociatedScaled = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + pixels[i].PackedValue = (uint)((byte)(i * 37 + 128) | ((byte)(i * 73 + 64) << 8) | ((byte)(i * 109 + 192) << 16) | ((byte)(i * 151 + 128) << 24)); + expectedNative[i] = pixels[i].ToVector4(); + expectedAssociatedNative[i] = pixels[i].ToAssociatedVector4(); + expectedAssociatedScaled[i] = pixels[i].ToAssociatedScaledVector4(); + expectedUnassociatedNative[i] = pixels[i].ToUnassociatedVector4(); + expectedUnassociatedScaled[i] = pixels[i].ToUnassociatedScaledVector4(); + } + + if (length > 0) + { + // PackedValue permits every signed byte pattern. Explicit -128 components ensure bulk sign extension matches scalar decoding even though the packer emits no -128 values. + pixels[0].PackedValue = 0x80808080; + expectedNative[0] = pixels[0].ToVector4(); + expectedAssociatedNative[0] = pixels[0].ToAssociatedVector4(); + expectedAssociatedScaled[0] = pixels[0].ToAssociatedScaledVector4(); + expectedUnassociatedNative[0] = pixels[0].ToUnassociatedVector4(); + expectedUnassociatedScaled[0] = pixels[0].ToUnassociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.None); + operations.ToVector4(Configuration.Default, pixels, actualAssociatedNative, PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, pixels, actualAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, pixels, actualUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.ToVector4(Configuration.Default, pixels, actualUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedAssociatedNative, actualAssociatedNative); + Assert.Equal(expectedAssociatedScaled, actualAssociatedScaled); + Assert.Equal(expectedUnassociatedNative, actualUnassociatedNative); + Assert.Equal(expectedUnassociatedScaled, actualUnassociatedScaled); + + Vector4[] unassociatedScaled = new Vector4[length]; + Vector4[] associatedScaled = new Vector4[length]; + Vector4[] unassociatedNative = new Vector4[length]; + Vector4[] associatedNative = new Vector4[length]; + NormalizedByte4P[] expectedFromUnassociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromAssociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromUnassociatedScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromAssociatedScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromUnassociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromAssociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromUnassociatedScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromAssociatedScaled = new NormalizedByte4P[length]; + + for (int i = 0; i < length; i++) + { + float alpha = ((i * 151) % 1021) / 1020F; + unassociatedScaled[i] = new Vector4(((i * 37) % 1021) / 1020F, ((i * 73) % 1021) / 1020F, ((i * 109) % 1021) / 1020F, alpha); + associatedScaled[i] = new Vector4(unassociatedScaled[i].X * alpha, unassociatedScaled[i].Y * alpha, unassociatedScaled[i].Z * alpha, alpha); + + // Signed-native formats encode the common scaled domain over [-1, 1], so native inputs must be derived before invoking the native entry points. + unassociatedNative[i] = (unassociatedScaled[i] * 2F) - Vector4.One; + associatedNative[i] = (associatedScaled[i] * 2F) - Vector4.One; + expectedFromUnassociatedNative[i] = NormalizedByte4P.FromUnassociatedVector4(unassociatedNative[i]); + expectedFromAssociatedNative[i] = NormalizedByte4P.FromAssociatedVector4(associatedNative[i]); + expectedFromUnassociatedScaled[i] = NormalizedByte4P.FromUnassociatedScaledVector4(unassociatedScaled[i]); + expectedFromAssociatedScaled[i] = NormalizedByte4P.FromAssociatedScaledVector4(associatedScaled[i]); + } + + Vector4[] unassociatedNativeSource = [.. unassociatedNative]; + Vector4[] associatedNativeSource = [.. associatedNative]; + Vector4[] unassociatedScaledSource = [.. unassociatedScaled]; + Vector4[] associatedScaledSource = [.. associatedScaled]; + + operations.FromVector4Destructive(Configuration.Default, unassociatedNativeSource, actualFromUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedNativeSource, actualFromAssociatedNative, PixelConversionModifiers.Premultiply); + operations.FromVector4Destructive(Configuration.Default, unassociatedScaledSource, actualFromUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedScaledSource, actualFromAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedFromUnassociatedNative, actualFromUnassociatedNative); + Assert.Equal(expectedFromAssociatedNative, actualFromAssociatedNative); + Assert.Equal(expectedFromUnassociatedScaled, actualFromUnassociatedScaled); + Assert.Equal(expectedFromAssociatedScaled, actualFromAssociatedScaled); + + NormalizedByte4P[] expectedFromNative = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromNative = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromScaled = new NormalizedByte4P[length]; + + for (int i = 0; i < length; i++) + { + expectedFromNative[i] = NormalizedByte4P.FromVector4(expectedNative[i]); + expectedFromScaled[i] = NormalizedByte4P.FromScaledVector4(expectedAssociatedScaled[i]); + } + + Vector4[] actualNativeSource = [.. expectedNative]; + Vector4[] actualScaledSource = [.. expectedAssociatedScaled]; + + operations.FromVector4Destructive(Configuration.Default, actualNativeSource, actualFromNative, PixelConversionModifiers.None); + operations.FromVector4Destructive(Configuration.Default, actualScaledSource, actualFromScaled, PixelConversionModifiers.Scale); + + Assert.Equal(expectedFromNative, actualFromNative); + Assert.Equal(expectedFromScaled, actualFromScaled); + } + } +} + +/// +/// Tests the pixel format. +/// +public class HalfVector4PTests : AssociatedAlphaPixelTests +{ + [Fact] + public void PackedValueMatchesHalfVector4ForAssociatedVector() + { + Vector4 associated = new(.1F, -.3F, .5F, -.7F); + + Assert.Equal(new HalfVector4(associated).PackedValue, new HalfVector4P(associated).PackedValue); + } + + [Fact] + public void ColorRoundTripDoesNotIntroduceAdditionalAssociationLoss() + { + HalfVector4P source = HalfVector4P.FromAssociatedScaledVector4(new Vector4(.125F, .25F, .375F, .5F)); + HalfVector4P expected = HalfVector4P.FromScaledVector4(source.ToScaledVector4()); + Color color = Color.FromPixel(source); + Color[] bulkColors = new Color[1]; + + Color.FromPixel(new[] { source }, bulkColors); + + Assert.Equal(source.ToScaledVector4(), color.ToScaledVector4()); + Assert.Equal(expected, color.ToPixel()); + Assert.Equal(source.ToScaledVector4(), bulkColors[0].ToScaledVector4()); + Assert.Equal(expected, bulkColors[0].ToPixel()); + } + + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertBulkConversionsMatchScalar() + { + AssertEveryHalfBitPatternUnpacksExactly(); + AssertHalfPackingMatchesScalar(); + AssertAlphaConversionsMatchScalar(); + } + + private static void AssertEveryHalfBitPatternUnpacksExactly() + { + HalfVector4P[] pixels = new HalfVector4P[(ushort.MaxValue + 1) / 4]; + Vector4[] expectedNative = new Vector4[pixels.Length]; + Vector4[] expectedScaled = new Vector4[pixels.Length]; + Vector4[] actualNative = new Vector4[pixels.Length]; + Vector4[] actualScaled = new Vector4[pixels.Length]; + + for (int i = 0; i < pixels.Length; i++) + { + ulong x = (ushort)(i * 4); + ulong y = (ushort)((i * 4) + 1); + ulong z = (ushort)((i * 4) + 2); + ulong w = (ushort)((i * 4) + 3); + pixels[i].PackedValue = x | (y << 16) | (z << 32) | (w << 48); + expectedNative[i] = pixels[i].ToVector4(); + expectedScaled[i] = pixels[i].ToScaledVector4(); + } + + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.None); + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualScaled, PixelConversionModifiers.Scale); + + AssertVectorBitsEqual(expectedNative, actualNative); + AssertVectorBitsEqual(expectedScaled, actualScaled); + } + + private static void AssertHalfPackingMatchesScalar() + { + const int finitePositiveHalfCount = 0x7BFF; + const int distributedFloatCount = ushort.MaxValue + 1; + const int componentCount = (ushort.MaxValue + 1) + (finitePositiveHalfCount * 2) + distributedFloatCount + 2; + Vector4[] source = new Vector4[componentCount / 4]; + Span components = MemoryMarshal.Cast(source.AsSpan()); + int index = 0; + + for (int bits = 0; bits <= ushort.MaxValue; bits++) + { + components[index++] = (float)BitConverter.UInt16BitsToHalf((ushort)bits); + } + + for (int bits = 0; bits < finitePositiveHalfCount; bits++) + { + float lower = (float)BitConverter.UInt16BitsToHalf((ushort)bits); + float upper = (float)BitConverter.UInt16BitsToHalf((ushort)(bits + 1)); + + // Every midpoint exercises binary16 round-to-nearest-even; negating it covers the symmetric sign path. + float midpoint = (lower + upper) * .5F; + components[index++] = midpoint; + components[index++] = -midpoint; + } + + // Equal high and low words distribute samples across the complete binary32 sign, exponent, and fraction space. + for (uint bits = 0; bits <= ushort.MaxValue; bits++) + { + components[index++] = BitConverter.UInt32BitsToSingle(bits * 0x0001_0001U); + } + + // The two padding components also verify finite overflow in both directions. + components[index++] = float.MaxValue; + components[index] = float.MinValue; + + HalfVector4P[] expected = new HalfVector4P[source.Length]; + HalfVector4P[] actual = new HalfVector4P[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + expected[i] = HalfVector4P.FromVector4(source[i]); + } + + Vector4[] destructiveSource = [.. source]; + + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actual, PixelConversionModifiers.None); + + for (int i = 0; i < expected.Length; i++) + { + for (int component = 0; component < 4; component++) + { + int shift = component * 16; + ushort expectedBits = (ushort)(expected[i].PackedValue >> shift); + ushort actualBits = (ushort)(actual[i].PackedValue >> shift); + + // Association performs floating-point arithmetic before packing. IEEE 754 requires a NaN result but does not specify which operand's payload survives that arithmetic. + if (Half.IsNaN(BitConverter.UInt16BitsToHalf(expectedBits))) + { + Assert.True(Half.IsNaN(BitConverter.UInt16BitsToHalf(actualBits)), $"Pixel {i}, component {component} should be NaN."); + } + else + { + Assert.Equal(expectedBits, actualBits); + } + } + } + } + + private static void AssertAlphaConversionsMatchScalar() + { + // Values immediately below, at, and above each vector width force every hardware regime through its vector body and scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + Vector4[] unassociatedScaled = new Vector4[length]; + Vector4[] associatedScaled = new Vector4[length]; + Vector4[] unassociatedNative = new Vector4[length]; + Vector4[] associatedNative = new Vector4[length]; + HalfVector4P[] expectedFromUnassociatedNative = new HalfVector4P[length]; + HalfVector4P[] expectedFromAssociatedNative = new HalfVector4P[length]; + HalfVector4P[] expectedFromUnassociatedScaled = new HalfVector4P[length]; + HalfVector4P[] expectedFromAssociatedScaled = new HalfVector4P[length]; + HalfVector4P[] actualFromUnassociatedNative = new HalfVector4P[length]; + HalfVector4P[] actualFromAssociatedNative = new HalfVector4P[length]; + HalfVector4P[] actualFromUnassociatedScaled = new HalfVector4P[length]; + HalfVector4P[] actualFromAssociatedScaled = new HalfVector4P[length]; + + for (int i = 0; i < length; i++) + { + float alpha = ((i * 151) % 4093) / 4092F; + unassociatedScaled[i] = new Vector4(((i * 37) % 4093) / 4092F, ((i * 73) % 4093) / 4092F, ((i * 109) % 4093) / 4092F, alpha); + associatedScaled[i] = new Vector4(unassociatedScaled[i].X * alpha, unassociatedScaled[i].Y * alpha, unassociatedScaled[i].Z * alpha, alpha); + + unassociatedNative[i] = (unassociatedScaled[i] * 131008F) - new Vector4(65504F); + associatedNative[i] = (associatedScaled[i] * 131008F) - new Vector4(65504F); + expectedFromUnassociatedNative[i] = HalfVector4P.FromUnassociatedVector4(unassociatedNative[i]); + expectedFromAssociatedNative[i] = HalfVector4P.FromAssociatedVector4(associatedNative[i]); + expectedFromUnassociatedScaled[i] = HalfVector4P.FromUnassociatedScaledVector4(unassociatedScaled[i]); + expectedFromAssociatedScaled[i] = HalfVector4P.FromAssociatedScaledVector4(associatedScaled[i]); + } + + Vector4[] unassociatedNativeSource = [.. unassociatedNative]; + Vector4[] associatedNativeSource = [.. associatedNative]; + Vector4[] unassociatedScaledSource = [.. unassociatedScaled]; + Vector4[] associatedScaledSource = [.. associatedScaled]; + + operations.FromVector4Destructive(Configuration.Default, unassociatedNativeSource, actualFromUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedNativeSource, actualFromAssociatedNative, PixelConversionModifiers.Premultiply); + operations.FromVector4Destructive(Configuration.Default, unassociatedScaledSource, actualFromUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedScaledSource, actualFromAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedFromUnassociatedNative, actualFromUnassociatedNative); + Assert.Equal(expectedFromAssociatedNative, actualFromAssociatedNative); + Assert.Equal(expectedFromUnassociatedScaled, actualFromUnassociatedScaled); + Assert.Equal(expectedFromAssociatedScaled, actualFromAssociatedScaled); + + Vector4[] expectedAssociatedNative = new Vector4[length]; + Vector4[] expectedAssociatedScaled = new Vector4[length]; + Vector4[] expectedUnassociatedNative = new Vector4[length]; + Vector4[] expectedUnassociatedScaled = new Vector4[length]; + Vector4[] actualAssociatedNative = new Vector4[length]; + Vector4[] actualAssociatedScaled = new Vector4[length]; + Vector4[] actualUnassociatedNative = new Vector4[length]; + Vector4[] actualUnassociatedScaled = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + expectedAssociatedNative[i] = expectedFromAssociatedScaled[i].ToAssociatedVector4(); + expectedAssociatedScaled[i] = expectedFromAssociatedScaled[i].ToAssociatedScaledVector4(); + expectedUnassociatedNative[i] = expectedFromAssociatedScaled[i].ToUnassociatedVector4(); + expectedUnassociatedScaled[i] = expectedFromAssociatedScaled[i].ToUnassociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualAssociatedNative, PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + AssertVectorBitsEqual(expectedAssociatedNative, actualAssociatedNative); + AssertVectorBitsEqual(expectedAssociatedScaled, actualAssociatedScaled); + AssertVectorBitsEqual(expectedUnassociatedNative, actualUnassociatedNative); + AssertVectorBitsEqual(expectedUnassociatedScaled, actualUnassociatedScaled); + } + } + + private static void AssertVectorBitsEqual(ReadOnlySpan expected, ReadOnlySpan actual) + { + ReadOnlySpan expectedBits = MemoryMarshal.Cast(expected); + ReadOnlySpan actualBits = MemoryMarshal.Cast(actual); + + Assert.Equal(expectedBits.ToArray(), actualBits.ToArray()); + } +} + +/// +/// Tests conversion between associated-alpha packed byte layouts. +/// +public class AssociatedAlphaPackedPixelConversionTests +{ + [Fact] + public void Rgba32PToBgra32PRoundTripIsLossless() => AssertLosslessRoundTrip(); + + [Fact] + public void Rgba32PToArgb32PRoundTripIsLossless() => AssertLosslessRoundTrip(); + + [Fact] + public void Rgba32PToAbgr32PRoundTripIsLossless() => AssertLosslessRoundTrip(); + + [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(); + + [Fact] + public void Bgra32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Argb32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Abgr32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Rgba32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Rgba32P(r, g, b, a)); + + [Fact] + public void Bgra32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Bgra32P(r, g, b, a)); + + [Fact] + public void Argb32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Argb32P(r, g, b, a)); + + [Fact] + public void Abgr32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Abgr32P(r, g, b, a)); + + [Fact] + public void Rgba32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void Bgra32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void Argb32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void Abgr32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() + { + // RemoteExecutor resolves the delegate by method name alone, so its entry point must not share the generic worker's name. + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertPackedBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + } + + private static void AssertPackedBulkConversionsMatchScalar() + { + AssertBulkConversionsMatchScalar((r, g, b, a) => new Rgba32P(r, g, b, a)); + AssertBulkConversionsMatchScalar((r, g, b, a) => new Bgra32P(r, g, b, a)); + AssertBulkConversionsMatchScalar((r, g, b, a) => new Argb32P(r, g, b, a)); + AssertBulkConversionsMatchScalar((r, g, b, a) => new Abgr32P(r, g, b, a)); + } + + private static void AssertBulkConversionsMatchScalar(Func createPixel) + where TPixel : unmanaged, IPixel + { + AssertScalarAndBulkAssociatedVectorsAreEqual(createPixel); + AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + AssertScalarAndBulkUnassociatedVectorsAreEqual(createPixel); + AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + } + + private static void AssertLosslessRoundTrip() + where TIntermediate : unmanaged, IPixel + { + const int componentAlphaPairCount = ((byte.MaxValue + 1) * (byte.MaxValue + 2)) / 2; + const int colorChannelCount = 3; + Rgba32P[] expected = new Rgba32P[componentAlphaPairCount * colorChannelCount]; + TIntermediate[] intermediate = new TIntermediate[expected.Length]; + Rgba32P[] actual = new Rgba32P[expected.Length]; + int index = 0; + + // Every component/alpha pair in the valid associated-byte domain must survive a layout + // conversion exactly. Exercising each color lane also catches an incorrect channel map. + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + for (int component = 0; component <= alpha; component++) + { + expected[index++] = new Rgba32P((byte)component, 0, 0, (byte)alpha); + expected[index++] = new Rgba32P(0, (byte)component, 0, (byte)alpha); + expected[index++] = new Rgba32P(0, 0, (byte)component, (byte)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, expected, intermediate); + PixelOperations.Instance.From(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(Func createPixel) + where TPixel : unmanaged, IPixel + { + // Prefixes straddle every vector width; the final length also preserves exhaustive byte-component coverage and adds a scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259, byte.MaxValue * 4 + 5]; + TPixel[] source = new TPixel[lengths[^1]]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + for (int component = 0; component <= byte.MaxValue; component++) + { + int index = component * 4; + source[index] = createPixel((byte)component, 0, 0, byte.MaxValue); + source[index + 1] = createPixel(0, (byte)component, 0, byte.MaxValue); + source[index + 2] = createPixel(0, 0, (byte)component, byte.MaxValue); + source[index + 3] = createPixel(0, 0, 0, (byte)component); + } + + source[^1] = createPixel(37, 73, 109, 151); + + foreach (int length in lengths) + { + ReadOnlySpan pixels = source.AsSpan(0, length); + Vector4[] expectedNative = new Vector4[length]; + Vector4[] expectedScaled = new Vector4[length]; + Vector4[] actualNative = new Vector4[length]; + Vector4[] actualScaled = new Vector4[length]; + + for (int i = 0; i < pixels.Length; i++) + { + expectedNative[i] = pixels[i].ToAssociatedVector4(); + expectedScaled[i] = pixels[i].ToAssociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, pixels, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } + + private static void AssertScalarAndBulkFromAssociatedVectorsAreEqual() + where TPixel : unmanaged, IPixel + { + // Values immediately below, at, and above each vector width force every hardware regime through its vector body and scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + Vector4[] source = new Vector4[lengths[^1]]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + for (int i = 0; i < source.Length; i++) + { + // Alternating exact byte alpha values with fractional values covers both reassociation branches. + float alpha = (i & 1) == 0 ? (i % 256) / 255F : ((i % 255) + .375F) / 255F; + source[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha; + source[i].W = alpha; + } + + foreach (int length in lengths) + { + Vector4[] nativeSource = source.AsSpan(0, length).ToArray(); + Vector4[] scaledSource = source.AsSpan(0, length).ToArray(); + TPixel[] expectedNative = new TPixel[length]; + TPixel[] expectedScaled = new TPixel[length]; + TPixel[] actualNative = new TPixel[length]; + TPixel[] actualScaled = new TPixel[length]; + + for (int i = 0; i < length; i++) + { + expectedNative[i] = TPixel.FromAssociatedVector4(nativeSource[i]); + expectedScaled[i] = TPixel.FromAssociatedScaledVector4(scaledSource[i]); + } + + operations.FromVector4Destructive(Configuration.Default, nativeSource, actualNative, PixelConversionModifiers.Premultiply); + operations.FromVector4Destructive(Configuration.Default, scaledSource, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } + + private static void AssertScalarAndBulkUnassociatedVectorsAreEqual(Func createPixel) + where TPixel : unmanaged, IPixel + { + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + TPixel[] pixels = new TPixel[length]; + Vector4[] expectedNative = new Vector4[length]; + Vector4[] expectedScaled = new Vector4[length]; + Vector4[] actualNative = new Vector4[length]; + Vector4[] actualScaled = new Vector4[length]; + + for (int i = 0; i < pixels.Length; i++) + { + // Independent component sequences exercise every packed layout across each SIMD width and remainder. + pixels[i] = createPixel((byte)((i * 37) % 256), (byte)((i * 73) % 256), (byte)((i * 109) % 256), (byte)((i * 151) % 256)); + expectedNative[i] = pixels[i].ToUnassociatedVector4(); + expectedScaled[i] = pixels[i].ToUnassociatedScaledVector4(); + } + + if (length > 0) + { + // A zero-alpha pixel with stored RGB verifies the converter's defined zero-divisor behavior outside the lossless straight-color round-trip domain. + pixels[0] = createPixel(37, 73, 109, 0); + expectedNative[0] = pixels[0].ToUnassociatedVector4(); + expectedScaled[0] = pixels[0].ToUnassociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.UnPremultiply); + operations.ToVector4(Configuration.Default, pixels, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } + + private static void AssertScalarAndBulkFromUnassociatedVectorsAreEqual() + where TPixel : unmanaged, IPixel + { + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + Vector4[] vectors = new Vector4[length]; + TPixel[] expectedNative = new TPixel[length]; + TPixel[] expectedScaled = new TPixel[length]; + TPixel[] actualNative = new TPixel[length]; + TPixel[] actualScaled = new TPixel[length]; + + for (int i = 0; i < vectors.Length; i++) + { + // Fractional alpha values verify destination-alpha quantization across each SIMD width and remainder. + vectors[i] = new Vector4(((i * 37) % 1021) / 1020F, ((i * 73) % 1021) / 1020F, ((i * 109) % 1021) / 1020F, ((i * 151) % 1021) / 1020F); + expectedNative[i] = TPixel.FromUnassociatedVector4(vectors[i]); + expectedScaled[i] = TPixel.FromUnassociatedScaledVector4(vectors[i]); + } + + Vector4[] nativeSource = [.. vectors]; + Vector4[] scaledSource = [.. vectors]; + + operations.FromVector4Destructive(Configuration.Default, nativeSource, actualNative, PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, scaledSource, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } +} + +/// +/// Tests conversion between associated and unassociated packed byte formats. +/// +public class AssociatedToUnassociatedPackedPixelConversionTests +{ + [Fact] + public void Rgba32ToRgba32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToBgra32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToArgb32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToAbgr32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToNormalizedByte4PMatchesExactAssociationForEveryComponentAndAlpha() + { + const int pairCount = 65536; + const int channelCount = 3; + Rgba32[] source = new Rgba32[pairCount * channelCount]; + NormalizedByte4P[] expected = new NormalizedByte4P[source.Length]; + NormalizedByte4P[] actualBulk = new NormalizedByte4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // NormalizedByte4 has 254 intervals from -1 through 1, so alpha is first rounded to that destination grid. + int destinationAlpha = ((alpha * 254) + 127) / byte.MaxValue; + + for (int unassociated = 0; unassociated <= byte.MaxValue; unassociated++) + { + // Association uses the alpha representable by the destination, then rounds the result to the same 254-interval grid. + int associated = ((unassociated * destinationAlpha) + 127) / byte.MaxValue; + + source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); + expected[index++] = CreateNormalizedByte4P(associated, 0, 0, destinationAlpha); + source[index] = new Rgba32(0, (byte)unassociated, 0, (byte)alpha); + expected[index++] = CreateNormalizedByte4P(0, associated, 0, destinationAlpha); + source[index] = new Rgba32(0, 0, (byte)unassociated, (byte)alpha); + expected[index++] = CreateNormalizedByte4P(0, 0, associated, destinationAlpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + + Assert.Equal(expected, actualBulk); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expected[i], NormalizedByte4P.FromRgba32(source[i])); + Assert.Equal(expected[i], Color.FromPixel(source[i]).ToPixel()); + } + } + + [Fact] + public void Rgba32ToHalfVector4PMatchesExactAssociationForEveryComponentAndAlpha() + { + const int pairCount = 65536; + const int channelCount = 3; + + const float finiteMinimum = -65504F; + const float finiteRange = 131008F; + const float inverseFiniteRange = (float)(1D / finiteRange); + const ulong zeroComponentBits = 0xFBFF; + Rgba32[] source = new Rgba32[pairCount * channelCount]; + HalfVector4P[] expected = new HalfVector4P[source.Length]; + HalfVector4P[] actualBulk = new HalfVector4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // Association must use the alpha value recovered from the destination half, rather than the higher-precision source alpha. + float normalizedAlpha = (float)(alpha / (double)byte.MaxValue); + ushort alphaBits = BitConverter.HalfToUInt16Bits((Half)((normalizedAlpha * finiteRange) + finiteMinimum)); + float destinationAlpha = ((float)BitConverter.UInt16BitsToHalf(alphaBits) * inverseFiniteRange) + .5F; + + for (int unassociated = 0; unassociated <= byte.MaxValue; unassociated++) + { + float normalizedComponent = (float)(unassociated / (double)byte.MaxValue); + float associated = normalizedComponent * destinationAlpha; + ushort associatedBits = BitConverter.HalfToUInt16Bits((Half)((associated * finiteRange) + finiteMinimum)); + ulong alphaPacked = (ulong)alphaBits << 48; + + source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); + expected[index++] = new HalfVector4P { PackedValue = associatedBits | (zeroComponentBits << 16) | (zeroComponentBits << 32) | alphaPacked }; + source[index] = new Rgba32(0, (byte)unassociated, 0, (byte)alpha); + expected[index++] = new HalfVector4P { PackedValue = zeroComponentBits | ((ulong)associatedBits << 16) | (zeroComponentBits << 32) | alphaPacked }; + source[index] = new Rgba32(0, 0, (byte)unassociated, (byte)alpha); + expected[index++] = new HalfVector4P { PackedValue = zeroComponentBits | (zeroComponentBits << 16) | ((ulong)associatedBits << 32) | alphaPacked }; + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expected[i], HalfVector4P.FromRgba32(source[i])); + Assert.Equal(expected[i], Color.FromPixel(source[i]).ToPixel()); + Assert.Equal(expected[i], actualBulk[i]); + } + } + + [Fact] + public void Rgba32PToRgba32ScalarRoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // Components greater than alpha are valid for additive blending but cannot round-trip losslessly through an 8-bit unassociated pixel. + // The exhaustive lossless domain is therefore 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 ColorFromRgba32PPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + const int pairCount = 32896; + Rgba32P[] sourcePixels = new Rgba32P[pairCount]; + Color[] bulkColors = new Color[pairCount]; + 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); + Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); + Color color = Color.FromPixel(source); + sourcePixels[index++] = source; + + Assert.Equal(new Rgba32(unassociated, 0, 0, (byte)alpha), color.ToPixel()); + Assert.Equal(source, color.ToPixel()); + } + } + + Color.FromPixel(sourcePixels, bulkColors); + + for (int i = 0; i < sourcePixels.Length; i++) + { + Assert.Equal(sourcePixels[i], bulkColors[i].ToPixel()); + } + } + + [Fact] + public void Rgba32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); + + [Fact] + public void Bgra32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); + + [Fact] + public void Argb32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); + + [Fact] + public void Abgr32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); + + [Fact] + public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + 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 ColorFromNormalizedByte4PPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + const int pairCount = 32640; + NormalizedByte4P[] sourcePixels = new NormalizedByte4P[pairCount]; + Color[] bulkColors = new Color[pairCount]; + 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); + NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); + Color color = Color.FromPixel(source); + sourcePixels[index++] = source; + + Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), color.ToPixel()); + Assert.Equal(source, color.ToPixel()); + } + } + + Color.FromPixel(sourcePixels, bulkColors); + + for (int i = 0; i < sourcePixels.Length; i++) + { + Assert.Equal(sourcePixels[i], bulkColors[i].ToPixel()); + } + } + + [Fact] + public void NormalizedByte4PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + 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.Instance.From(Configuration.Default, source, actualUnassociated); + PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + + for (int i = 0; i < source.Length; i++) + { + Bgra32 expected = expectedUnassociated[i]; + + // Scalar conversion and Color must preserve the same exact canonical value proven for the bulk path. + Assert.Equal(new Rgba32(expected.R, expected.G, expected.B, expected.A), source[i].ToRgba32()); + Assert.Equal(expected, Color.FromPixel(source[i]).ToPixel()); + } + } + + private static void AssertUnsignedByteBulkRoundTrip(Func createPixel) + where TPixel : unmanaged, IPixel + { + 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++) + { + // Components greater than alpha are valid for additive blending but cannot round-trip losslessly through an 8-bit unassociated pixel. + // The exhaustive lossless domain is therefore 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.Instance.From(Configuration.Default, source, actualUnassociated); + PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + + for (int i = 0; i < source.Length; i++) + { + Bgra32 expected = expectedUnassociated[i]; + + // Scalar conversion and Color must use the same exact canonical byte as the independently calculated bulk oracle. + Assert.Equal(new Rgba32(expected.R, expected.G, expected.B, expected.A), source[i].ToRgba32()); + Assert.Equal(expected, Color.FromPixel(source[i]).ToPixel()); + } + } + + private static void AssertUnsignedByteAssociation(Func createPixel) + where TPixel : unmanaged, IPixel + { + const int pairCount = 65536; + const int channelCount = 3; + Rgba32[] source = new Rgba32[pairCount * channelCount]; + TPixel[] expected = new TPixel[source.Length]; + TPixel[] actualBulk = new TPixel[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + for (int unassociated = 0; unassociated <= byte.MaxValue; unassociated++) + { + // The destination stores round(unassociated * alpha / 255). The integer numerator adds half + // the divisor so the expected value is independent of the floating-point implementation. + byte associated = (byte)(((unassociated * alpha) + 127) / byte.MaxValue); + + source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); + expected[index++] = createPixel(associated, 0, 0, (byte)alpha); + source[index] = new Rgba32(0, (byte)unassociated, 0, (byte)alpha); + expected[index++] = createPixel(0, associated, 0, (byte)alpha); + source[index] = new Rgba32(0, 0, (byte)unassociated, (byte)alpha); + expected[index++] = createPixel(0, 0, associated, (byte)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + + Assert.Equal(expected, actualBulk); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expected[i], TPixel.FromRgba32(source[i])); + Assert.Equal(expected[i], Color.FromPixel(source[i]).ToPixel()); + } + } + + 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 }; + } +} + +/// +/// Tests that associated destinations use their stored alpha value when associating color components. +/// +public class AssociatedDestinationAlphaQuantizationTests +{ + [Fact] + public void Rgba32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void Bgra32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void Argb32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void Abgr32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void NormalizedByte4PQuantizesDestinationAlphaBeforeAssociation() + { + ReadOnlySpan 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.Instance.From(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() + { + const float finiteMinimum = -65504F; + const float finiteRange = 131008F; + const float inverseFiniteRange = (float)(1D / finiteRange); + ReadOnlySpan 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.Instance.From(Configuration.Default, source, actualBulk); + index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + float normalizedAlpha = alpha / (float)ushort.MaxValue; + ushort expectedAlpha = BitConverter.HalfToUInt16Bits((Half)((normalizedAlpha * finiteRange) + finiteMinimum)); + float storedAlpha = ((float)BitConverter.UInt16BitsToHalf(expectedAlpha) * inverseFiniteRange) + .5F; + + foreach (byte component in components) + { + float associatedRed = (component / (float)byte.MaxValue) * storedAlpha; + ushort expectedRed = BitConverter.HalfToUInt16Bits((Half)((associatedRed * finiteRange) + finiteMinimum)); + 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 RgbaHalfPQuantizesDestinationAlphaBeforeAssociation() + { + ReadOnlySpan components = [64, 127, 191]; + Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; + RgbaHalfP[] actualBulk = new RgbaHalfP[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.Instance.From(Configuration.Default, source, actualBulk); + index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + float normalizedAlpha = alpha / (float)ushort.MaxValue; + Half expectedAlpha = (Half)normalizedAlpha; + float storedAlpha = (float)expectedAlpha; + + foreach (byte component in components) + { + Half expectedRed = (Half)((component / (float)byte.MaxValue) * storedAlpha); + RgbaHalfP actualScalar = RgbaHalfP.FromRgba64(source[index]); + + Assert.Equal(expectedRed, actualScalar.R); + Assert.Equal(expectedAlpha, actualScalar.A); + Assert.Equal(expectedRed, actualBulk[index].R); + Assert.Equal(expectedAlpha, actualBulk[index].A); + index++; + } + } + } + + [Fact] + public void ColorToRgba32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToBgra32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToArgb32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToAbgr32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToNormalizedByte4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToHalfVector4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToRgbaHalfPUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + /// + /// Verifies the unsigned-byte destination grid through scalar and bulk conversion entry points. + /// + /// The associated unsigned-byte pixel format. + private static void AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation() + where TPixel : unmanaged, IPixel + { + ReadOnlySpan 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.Instance.From(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) / byte.MaxValue; + + Assert.Equal(expected, TPixel.FromRgba64(source[index]).ToScaledVector4()); + Assert.Equal(expected, actualBulk[index].ToScaledVector4()); + index++; + } + } + } + + /// + /// Verifies that delegates association to the destination pixel operations. + /// + /// The associated destination pixel format. + private static void AssertColorUsesDestinationAlphaRepresentation() + where TPixel : unmanaged, IPixel + { + 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(); + + Assert.Equal(expected, actual); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 9366c51c98..3e06f04714 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -26,45 +27,62 @@ public void HalfSingle_PackedValue() public void HalfSingle_ToVector4() { // arrange - HalfSingle pixel = new(0.5f); - Vector4 expected = new(0.5f, 0, 0, 1); + HalfSingle pixel = new(-2F); + Vector4 expected = new(-2F, 0, 0, 1); // act Vector4 actual = pixel.ToVector4(); // assert Assert.Equal(expected, actual); + Assert.Equal((float)Half.MinValue, new HalfSingle((float)Half.MinValue).ToSingle()); + Assert.Equal((float)Half.MaxValue, new HalfSingle((float)Half.MaxValue).ToSingle()); } [Fact] public void HalfSingle_ToScaledVector4() { - // arrange - HalfSingle pixel = new(-1F); - - // act - Vector4 actual = pixel.ToScaledVector4(); - - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(new Vector4(0F, 0F, 0F, 1F), new HalfSingle((float)Half.MinValue).ToScaledVector4()); + Assert.Equal(new Vector4(.5F, 0F, 0F, 1F), new HalfSingle(0F).ToScaledVector4()); + Assert.Equal(new Vector4(1F, 0F, 0F, 1F), new HalfSingle((float)Half.MaxValue).ToScaledVector4()); } [Fact] public void HalfSingle_FromScaledVector4() { - // arrange - Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); - const int expected = 48128; + Assert.Equal((ushort)0xFBFF, HalfSingle.FromScaledVector4(new Vector4(0F, 0F, 0F, 1F)).PackedValue); + Assert.Equal((ushort)0, HalfSingle.FromScaledVector4(new Vector4(.5F, 0F, 0F, 1F)).PackedValue); + Assert.Equal((ushort)0x7BFF, HalfSingle.FromScaledVector4(new Vector4(1F, 0F, 0F, 1F)).PackedValue); + } - // act - HalfSingle pixel = HalfSingle.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; + [Fact] + public void HalfSingle_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfSingleBulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - // assert - Assert.Equal(expected, actual); + private static void AssertHalfSingleBulkScaledConversionsCoverFiniteRange() + { + ushort[] packedValues = [0xFBFF, 0, 0x7BFF]; + Vector4[] scaledValues = [new(0F, 0F, 0F, 1F), new(.5F, 0F, 0F, 1F), new(1F, 0F, 0F, 1F)]; + HalfSingle[] source = new HalfSingle[17]; + Vector4[] expectedVectors = new Vector4[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + int valueIndex = i % packedValues.Length; + source[i].PackedValue = packedValues[valueIndex]; + expectedVectors[i] = scaledValues[valueIndex]; + } + + Vector4[] actualVectors = new Vector4[source.Length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + HalfSingle[] actualPixels = new HalfSingle[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index c5a89df1e9..1fde34a826 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -25,19 +26,21 @@ public void HalfVector2_ToVector2() Assert.Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2()); Assert.Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2()); Assert.Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2()); + Assert.Equal(new Vector2(-2F, 4F), new HalfVector2(-2F, 4F).ToVector2()); + Assert.Equal(new Vector2((float)Half.MinValue, (float)Half.MaxValue), new HalfVector2((float)Half.MinValue, (float)Half.MaxValue).ToVector2()); } [Fact] public void HalfVector2_ToScaledVector4() { // arrange - HalfVector2 halfVector = new(Vector2.One); + HalfVector2 halfVector = new((float)Half.MinValue, (float)Half.MaxValue); // act Vector4 actual = halfVector.ToScaledVector4(); // assert - Assert.Equal(1F, actual.X); + Assert.Equal(0F, actual.X); Assert.Equal(1F, actual.Y); Assert.Equal(0, actual.Z); Assert.Equal(1, actual.W); @@ -47,8 +50,8 @@ public void HalfVector2_ToScaledVector4() public void HalfVector2_FromScaledVector4() { // arrange - Vector4 scaled = new HalfVector2(Vector2.One).ToScaledVector4(); - const uint expected = 1006648320u; + Vector4 scaled = new(0F, 1F, 0F, 1F); + const uint expected = 0x7BFF_FBFF; // act HalfVector2 halfVector = HalfVector2.FromScaledVector4(scaled); @@ -58,6 +61,30 @@ public void HalfVector2_FromScaledVector4() Assert.Equal(expected, actual); } + [Fact] + public void HalfVector2_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfVector2BulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertHalfVector2BulkScaledConversionsCoverFiniteRange() + { + HalfVector2 pixel = new() { PackedValue = 0x7BFF_FBFF }; + HalfVector2[] source = new HalfVector2[17]; + Vector4[] expectedVectors = new Vector4[source.Length]; + Array.Fill(source, pixel); + Array.Fill(expectedVectors, new Vector4(0F, 1F, 0F, 1F)); + + Vector4[] actualVectors = new Vector4[source.Length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + HalfVector2[] actualPixels = new HalfVector2[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); + } + [Fact] public void HalfVector2_ToVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index 16c78a23d3..75130c144c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -33,30 +34,32 @@ public void HalfVector4_ToVector4() Assert.Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4()); Assert.Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4()); Assert.Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4()); + Assert.Equal(new Vector4(-2F, 4F, .5F, 8F), new HalfVector4(-2F, 4F, .5F, 8F).ToVector4()); + Assert.Equal( + new Vector4((float)Half.MinValue, (float)Half.MaxValue, (float)Half.MinValue, (float)Half.MaxValue), + new HalfVector4((float)Half.MinValue, (float)Half.MaxValue, (float)Half.MinValue, (float)Half.MaxValue).ToVector4()); } [Fact] public void HalfVector4_ToScaledVector4() { // arrange - HalfVector4 pixel = new(-Vector4.One); + Vector4 expected = new(0F, .5F, 1F, 0F); + HalfVector4 pixel = new((float)Half.MinValue, 0F, (float)Half.MaxValue, (float)Half.MinValue); // act Vector4 actual = pixel.ToScaledVector4(); // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(0, actual.W); + Assert.Equal(expected, actual); } [Fact] public void HalfVector4_FromScaledVector4() { // arrange - Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); - const ulong expected = 13547034390470638592uL; + Vector4 scaled = new(0F, .25F, .5F, 1F); + ulong expected = new HalfVector4((float)Half.MinValue, -32752F, 0F, (float)Half.MaxValue).PackedValue; // act HalfVector4 pixel = HalfVector4.FromScaledVector4(scaled); @@ -66,6 +69,31 @@ public void HalfVector4_FromScaledVector4() Assert.Equal(expected, actual); } + [Fact] + public void HalfVector4_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfVector4BulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertHalfVector4BulkScaledConversionsCoverFiniteRange() + { + const ulong packedValue = 0xFBFF_7BFF_0000_FBFF; + HalfVector4 pixel = new() { PackedValue = packedValue }; + HalfVector4[] source = new HalfVector4[17]; + Vector4[] expectedVectors = new Vector4[source.Length]; + Array.Fill(source, pixel); + Array.Fill(expectedVectors, new Vector4(0F, .5F, 1F, 0F)); + + Vector4[] actualVectors = new Vector4[source.Length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + HalfVector4[] actualPixels = new HalfVector4[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); + } + [Fact] public void HalfVector4_FromBgra5551() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index ffbddb1398..e6be539c9a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -29,6 +30,42 @@ public void NormalizedByte2_ToVector2() Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); } + [Fact] + public void NormalizedByte2_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedByte2MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedByte2MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedByte2 pixel = new() { PackedValue = 0x8080 }; + Vector4 expectedNative = new(-1F, -1F, 0F, 1F); + Vector4 expectedScaled = new(0F, 0F, 0F, 1F); + + Assert.Equal(expectedNative, pixel.ToVector4()); + Assert.Equal(expectedScaled, pixel.ToScaledVector4()); + + NormalizedByte2[] source = new NormalizedByte2[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expectedNative, native[i]); + Assert.Equal(expectedScaled, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + Array.Fill(destructiveSource, expectedScaled); + NormalizedByte2[] actualPixels = new NormalizedByte2[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal((ushort)0x8181, actual.PackedValue)); + } + [Fact] public void NormalizedByte2_ToVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 5a025f6c4e..c2a0047d23 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -60,6 +61,39 @@ public void NormalizedByte4_ToVector4() Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); } + [Fact] + public void NormalizedByte4_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedByte4MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedByte4MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedByte4 pixel = new() { PackedValue = 0x80808080 }; + + Assert.Equal(-Vector4.One, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + + NormalizedByte4[] source = new NormalizedByte4[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(-Vector4.One, native[i]); + Assert.Equal(Vector4.Zero, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + NormalizedByte4[] actualPixels = new NormalizedByte4[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal(0x81818181U, actual.PackedValue)); + } + [Fact] public void NormalizedByte4_ToScaledVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index be7b390527..9cb3aee508 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -33,6 +34,42 @@ public void NormalizedShort2_ToVector2() Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); } + [Fact] + public void NormalizedShort2_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedShort2MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedShort2MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedShort2 pixel = new() { PackedValue = 0x80008000 }; + Vector4 expectedNative = new(-1F, -1F, 0F, 1F); + Vector4 expectedScaled = new(0F, 0F, 0F, 1F); + + Assert.Equal(expectedNative, pixel.ToVector4()); + Assert.Equal(expectedScaled, pixel.ToScaledVector4()); + + NormalizedShort2[] source = new NormalizedShort2[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expectedNative, native[i]); + Assert.Equal(expectedScaled, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + Array.Fill(destructiveSource, expectedScaled); + NormalizedShort2[] actualPixels = new NormalizedShort2[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal(0x80018001U, actual.PackedValue)); + } + [Fact] public void NormalizedShort2_ToVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index 281ae7ee52..c317586e48 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -61,6 +62,39 @@ public void NormalizedShort4_ToVector4() Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); } + [Fact] + public void NormalizedShort4_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedShort4MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedShort4MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedShort4 pixel = new() { PackedValue = 0x8000800080008000 }; + + Assert.Equal(-Vector4.One, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + + NormalizedShort4[] source = new NormalizedShort4[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(-Vector4.One, native[i]); + Assert.Equal(Vector4.Zero, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + NormalizedShort4[] actualPixels = new NormalizedShort4[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal(0x8001800180018001UL, actual.PackedValue)); + } + [Fact] public void NormalizedShort4_ToScaledVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs new file mode 100644 index 0000000000..2cc26e76ca --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs @@ -0,0 +1,548 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles.Companding; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Verifies scalar and bulk alpha-representation conversions for a pixel format. +/// +/// The pixel format. +[Trait("Category", "PixelFormats")] +public abstract class PixelAlphaRepresentationTests + where TPixel : unmanaged, IPixel +{ + private static readonly Vector4[] UnassociatedScaledVectors = + [ + new(.8F, .4F, .2F, .5F), + new(.15F, .65F, .35F, .25F), + new(.9F, .1F, .7F, 1F), + Vector4.Zero + ]; + + private static readonly Vector4[] OutOfRangeUnassociatedScaledVectors = + [ + new(-.25F, 1.25F, .5F, .75F), + new(1.5F, -.5F, 2F, 1F) + ]; + + [Fact] + public void ScalarScaledConversionsDescribeTheSameLogicalColors() + { + foreach (Vector4 unassociated in UnassociatedScaledVectors) + { + Vector4 associated = Associate(unassociated); + TPixel fromUnassociated = TPixel.FromUnassociatedScaledVector4(unassociated); + TPixel fromAssociated = TPixel.FromAssociatedScaledVector4(associated); + + Assert.Equal(fromUnassociated, fromAssociated); + } + } + + [Fact] + public void ScalarAssociatedVectorsContainPremultipliedRgb() + { + foreach (Vector4 source in UnassociatedScaledVectors) + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(source); + Vector4 unassociatedScaled = pixel.ToUnassociatedScaledVector4(); + Vector4 expectedAssociatedScaled = Associate(unassociatedScaled); + Vector4 actualAssociatedScaled = pixel.ToAssociatedScaledVector4(); + + if (typeof(TPixel) == typeof(NormalizedByte4P)) + { + // Exhaustive valid-component coverage proves that signed-byte unassociation followed by reassociation can differ by at most two ULP. + AlphaRepresentationTestAssertions.EqualWithinTwoUlps(expectedAssociatedScaled, actualAssociatedScaled); + } + else + { + // Reversing association can introduce one final float rounding step, but this still rejects an unassociated no-op by several orders of magnitude. + AlphaRepresentationTestAssertions.EqualWithinOneUlp(expectedAssociatedScaled, actualAssociatedScaled); + } + + Assert.Equal(BitConverter.SingleToInt32Bits(expectedAssociatedScaled.W), BitConverter.SingleToInt32Bits(actualAssociatedScaled.W)); + + Vector4 unassociatedNative = pixel.ToUnassociatedVector4(); + + if (unassociatedNative == unassociatedScaled) + { + // Native-equals-scaled formats use the same independent oracle; affine-native formats are verified by AffineNativeAlphaRepresentationTests. + AlphaRepresentationTestAssertions.EqualWithinOneUlp(expectedAssociatedScaled, pixel.ToAssociatedVector4()); + } + } + } + + [Fact] + public void ScalarScaledConversionsClampHighAlphaConsistently() + { + Vector4 unassociated = new(.8F, .4F, .2F, 1.5F); + Vector4 associated = Associate(unassociated); + + // Both entry points describe the same logical color, so clamping opacity at the storage boundary must not change RGB differently between them. + Assert.Equal(TPixel.FromUnassociatedScaledVector4(unassociated), TPixel.FromAssociatedScaledVector4(associated)); + } + + [Fact] + public void ScalarNativeConversionsRoundTripTheirDeclaredRepresentations() + { + foreach (Vector4 source in UnassociatedScaledVectors) + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(source); + TPixel fromUnassociated = TPixel.FromUnassociatedVector4(pixel.ToUnassociatedVector4()); + TPixel fromAssociated = TPixel.FromAssociatedVector4(pixel.ToAssociatedVector4()); + + Assert.Equal(pixel, fromUnassociated); + + if (pixel.ToUnassociatedScaledVector4().W == 0F) + { + // Associated color cannot encode hidden RGB at zero alpha. The round trip must therefore produce the format's transparent black value. + Assert.Equal(TPixel.FromUnassociatedScaledVector4(Vector4.Zero), fromAssociated); + } + else + { + Assert.Equal(pixel, fromAssociated); + } + } + } + + [Fact] + public void ParameterlessVectorConversionsUseTheDeclaredAlphaRepresentation() + { + PixelAlphaRepresentation alphaRepresentation = TPixel.GetPixelTypeInfo().AlphaRepresentation; + + foreach (Vector4 source in UnassociatedScaledVectors) + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(source); + Vector4 expectedNative = alphaRepresentation == PixelAlphaRepresentation.Associated + ? pixel.ToAssociatedVector4() + : pixel.ToUnassociatedVector4(); + + Vector4 expectedScaled = alphaRepresentation == PixelAlphaRepresentation.Associated + ? pixel.ToAssociatedScaledVector4() + : pixel.ToUnassociatedScaledVector4(); + + TPixel expectedFromNative = alphaRepresentation == PixelAlphaRepresentation.Associated + ? TPixel.FromAssociatedVector4(expectedNative) + : TPixel.FromUnassociatedVector4(expectedNative); + + TPixel expectedFromScaled = alphaRepresentation == PixelAlphaRepresentation.Associated + ? TPixel.FromAssociatedScaledVector4(expectedScaled) + : TPixel.FromUnassociatedScaledVector4(expectedScaled); + + Assert.Equal(expectedNative, pixel.ToVector4()); + Assert.Equal(expectedScaled, pixel.ToScaledVector4()); + Assert.Equal(expectedFromNative, TPixel.FromVector4(expectedNative)); + Assert.Equal(expectedFromScaled, TPixel.FromScaledVector4(expectedScaled)); + } + } + + [Fact] + public void BulkConversionsMatchScalarConversions() + { + const int length = 259; + TPixel[] pixels = new TPixel[length]; + + for (int i = 0; i < pixels.Length; i++) + { + Vector4 source = UnassociatedScaledVectors[i % UnassociatedScaledVectors.Length]; + pixels[i] = TPixel.FromUnassociatedScaledVector4(source); + } + + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToUnassociatedVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.UnPremultiply)); + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToAssociatedVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.Premultiply)); + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToUnassociatedScaledVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply)); + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToAssociatedScaledVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToUnassociatedVector4(), static vector => TPixel.FromUnassociatedVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.UnPremultiply)); + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToAssociatedVector4(), static vector => TPixel.FromAssociatedVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Premultiply)); + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToUnassociatedScaledVector4(), static vector => TPixel.FromUnassociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply)); + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToAssociatedScaledVector4(), static vector => TPixel.FromAssociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + [Fact] + public void BulkScaledConversionsClampLikeScalarConversions() + { + Vector4[] associated = new Vector4[OutOfRangeUnassociatedScaledVectors.Length]; + + for (int i = 0; i < associated.Length; i++) + { + associated[i] = Associate(OutOfRangeUnassociatedScaledVectors[i]); + } + + AssertBulkFromMatchesScalar(OutOfRangeUnassociatedScaledVectors, static vector => TPixel.FromUnassociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply)); + AssertBulkFromMatchesScalar(associated, static vector => TPixel.FromAssociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void BulkCompandingHonorsRequestedAlphaRepresentationAndRange(bool scaled, bool associated) + { + const int length = 259; + TPixel[] pixels = new TPixel[length]; + + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = TPixel.FromUnassociatedScaledVector4(UnassociatedScaledVectors[i % UnassociatedScaledVectors.Length]); + } + + PixelConversionModifiers modifiers = PixelConversionModifiers.SRgbCompand + | (scaled ? PixelConversionModifiers.Scale : PixelConversionModifiers.None) + | (associated ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.UnPremultiply); + + Vector4[] expectedVectors = new Vector4[length]; + + for (int i = 0; i < pixels.Length; i++) + { + expectedVectors[i] = scaled ? pixels[i].ToUnassociatedScaledVector4() : pixels[i].ToUnassociatedVector4(); + } + + // Transfer functions operate on straight RGB. Association is therefore applied after expansion on the outbound path. + SRgbCompanding.Expand(expectedVectors); + + if (associated) + { + Numerics.Premultiply(expectedVectors); + } + + Vector4[] actualVectors = new Vector4[length]; + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualVectors, modifiers); + + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] expectedPixelSource = [.. expectedVectors]; + + if (associated) + { + Numerics.UnPremultiply(expectedPixelSource); + } + + // Reverse the transfer only after restoring straight RGB, matching the observable modifier order for inbound vectors. + SRgbCompanding.Compress(expectedPixelSource); + + TPixel[] expectedPixels = new TPixel[length]; + + for (int i = 0; i < expectedPixels.Length; i++) + { + expectedPixels[i] = scaled + ? TPixel.FromUnassociatedScaledVector4(expectedPixelSource[i]) + : TPixel.FromUnassociatedVector4(expectedPixelSource[i]); + } + + Vector4[] actualPixelSource = [.. expectedVectors]; + TPixel[] actualPixels = new TPixel[length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, actualPixelSource, actualPixels, modifiers); + + Assert.Equal(expectedPixels, actualPixels); + } + + private static void AssertBulkToMatchesScalar(TPixel[] pixels, Func scalar, BulkToVector4 bulk) + { + Vector4[] expected = new Vector4[pixels.Length]; + Vector4[] actual = new Vector4[pixels.Length + 3]; + Vector4 sentinel = new(.125F, .25F, .5F, .75F); + + for (int i = 0; i < pixels.Length; i++) + { + expected[i] = scalar(pixels[i]); + } + + actual.AsSpan(pixels.Length).Fill(sentinel); + bulk(PixelOperations.Instance, pixels, actual); + + // Bulk conversion is source-length driven and must leave excess destination capacity untouched. + Assert.Equal(expected, actual[..pixels.Length]); + Assert.All(actual[pixels.Length..], vector => Assert.Equal(sentinel, vector)); + } + + private static void AssertBulkFromMatchesScalar(TPixel[] pixels, Func createSource, Func scalar, BulkFromVector4 bulk) + { + Vector4[] source = new Vector4[pixels.Length]; + + for (int i = 0; i < pixels.Length; i++) + { + source[i] = createSource(pixels[i]); + } + + AssertBulkFromMatchesScalar(source, scalar, bulk); + } + + private static void AssertBulkFromMatchesScalar(Vector4[] source, Func scalar, BulkFromVector4 bulk) + { + TPixel[] expected = new TPixel[source.Length]; + TPixel[] actual = new TPixel[source.Length + 3]; + TPixel sentinel = TPixel.FromUnassociatedScaledVector4(new Vector4(.125F, .25F, .5F, .75F)); + + for (int i = 0; i < source.Length; i++) + { + expected[i] = scalar(source[i]); + } + + // From-vector bulk operations are destructive, so preserve the scalar source for diagnostics and future assertions. + Vector4[] destructiveSource = [.. source]; + actual.AsSpan(source.Length).Fill(sentinel); + bulk(PixelOperations.Instance, destructiveSource, actual); + + // A destination may expose spare capacity; the source span still defines how many pixels are written. + Assert.Equal(expected, actual[..source.Length]); + Assert.All(actual[source.Length..], pixel => Assert.Equal(sentinel, pixel)); + } + + private static Vector4 Associate(Vector4 vector) + { + vector.X *= vector.W; + vector.Y *= vector.W; + vector.Z *= vector.W; + return vector; + } + + private delegate void BulkToVector4(PixelOperations operations, ReadOnlySpan source, Span destination); + + private delegate void BulkFromVector4(PixelOperations operations, Span source, Span destination); +} + +/// +/// Verifies the shared bulk vector conversion used by RGB byte formats. +/// +[Trait("Category", "PixelFormats")] +public class RgbaCompatiblePixelOperationsTests +{ + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertBulkConversionsMatchScalar() + { + AssertBulkConversionsMatchScalarForPixel(); + AssertBulkConversionsMatchScalarForPixel(); + } + + private static void AssertBulkConversionsMatchScalarForPixel() + where TPixel : unmanaged, IPixel + { + const int length = 259; + Vector4 sourceVector = new(.5F, .25F, .125F, 1F); + TPixel expectedPixel = TPixel.FromUnassociatedScaledVector4(sourceVector); + TPixel[] pixels = new TPixel[length]; + Array.Fill(pixels, expectedPixel); + + Vector4 expectedVector = expectedPixel.ToUnassociatedScaledVector4(); + Vector4 vectorSentinel = new(.125F, .5F, .75F, 1F); + Vector4[] vectors = new Vector4[length + 3]; + vectors.AsSpan(length).Fill(vectorSentinel); + + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, vectors, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.All(vectors[..length], vector => Assert.Equal(expectedVector, vector)); + Assert.All(vectors[length..], vector => Assert.Equal(vectorSentinel, vector)); + + Vector4[] source = new Vector4[length]; + source.AsSpan().Fill(sourceVector); + + TPixel pixelSentinel = TPixel.FromUnassociatedScaledVector4(vectorSentinel); + TPixel[] destination = new TPixel[length + 3]; + destination.AsSpan(length).Fill(pixelSentinel); + + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + // A 259-pixel source crosses the 128- and 256-bit optimized thresholds while leaving spare destination capacity to detect writes beyond the source length. + Assert.All(destination[..length], pixel => Assert.Equal(expectedPixel, pixel)); + Assert.All(destination[length..], pixel => Assert.Equal(pixelSentinel, pixel)); + } +} + +/// +/// Verifies alpha conversion for formats with distinct native vector contracts. +/// +[Trait("Category", "PixelFormats")] +public class AffineNativeAlphaRepresentationTests +{ + private static readonly Vector4 UnassociatedScaled = new(.8F, .4F, .2F, .5F); + + [Fact] + public void Byte4NativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => vector * byte.MaxValue); + + [Fact] + public void Short4NativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * ushort.MaxValue) - new Vector4(32768F)); + + [Fact] + public void NormalizedByte4NativeConversionsUseScaledAlpha() + => AssertSignedNormalizedNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void NormalizedByte4PNativeConversionsUseScaledAlpha() + => AssertSignedNormalizedNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void NormalizedShort4NativeConversionsUseScaledAlpha() + => AssertSignedNormalizedNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void HalfVector4NativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * 131008F) - new Vector4(65504F)); + + [Fact] + public void HalfVector4PNativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * 131008F) - new Vector4(65504F)); + + [Fact] + public void HalfSingleFromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 131008F) - 65504F, 0F, 0F, vector.W)); + + [Fact] + public void HalfVector2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 131008F) - 65504F, (vector.Y * 131008F) - 65504F, 0F, vector.W)); + + [Fact] + public void NormalizedByte2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, (vector.Y * 2F) - 1F, 0F, vector.W)); + + [Fact] + public void NormalizedShort2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, (vector.Y * 2F) - 1F, 0F, vector.W)); + + [Fact] + public void Short2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * ushort.MaxValue) - 32768F, (vector.Y * ushort.MaxValue) - 32768F, 0F, vector.W)); + + private static void AssertNativeConversions(Func encodeNative) + where TPixel : unmanaged, IPixel + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(UnassociatedScaled); + Vector4 unassociatedScaled = pixel.ToUnassociatedScaledVector4(); + Vector4 associatedScaled = Associate(unassociatedScaled); + + // Native alpha can be signed or use an integer component range, so multiplying native RGB by native W would not represent opacity. + AlphaRepresentationTestAssertions.EqualWithinOneUlp(encodeNative(unassociatedScaled), pixel.ToUnassociatedVector4()); + AlphaRepresentationTestAssertions.EqualWithinOneUlp(encodeNative(associatedScaled), pixel.ToAssociatedVector4()); + Assert.Equal(pixel, TPixel.FromUnassociatedVector4(encodeNative(unassociatedScaled))); + Assert.Equal(pixel, TPixel.FromAssociatedVector4(encodeNative(associatedScaled))); + } + + private static void AssertSignedNormalizedNativeConversions(Func encodeNative) + where TPixel : unmanaged, IPixel + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(UnassociatedScaled); + Vector4 unassociatedScaled = pixel.ToUnassociatedScaledVector4(); + Vector4 associatedScaled = Associate(unassociatedScaled); + + // The 2*x-1 affine map is ill-conditioned around native zero, so a native-space ULP tolerance would not measure conversion accuracy. + // Verify the stored native representation exactly, then verify both inverse paths using independently encoded scaled vectors. + if (TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated) + { + Assert.Equal(pixel.ToVector4(), pixel.ToAssociatedVector4()); + } + else + { + Assert.Equal(pixel.ToVector4(), pixel.ToUnassociatedVector4()); + } + + Assert.Equal(pixel, TPixel.FromUnassociatedVector4(encodeNative(unassociatedScaled))); + Assert.Equal(pixel, TPixel.FromAssociatedVector4(encodeNative(associatedScaled))); + } + + private static void AssertAlphaLessNativeFrom(Func encodeNative) + where TPixel : unmanaged, IPixel + { + TPixel expected = TPixel.FromUnassociatedScaledVector4(UnassociatedScaled); + Vector4 associatedScaled = Associate(UnassociatedScaled); + + // Formats with implicit alpha still need to unassociate an associated source before discarding its W component. + Assert.Equal(expected, TPixel.FromAssociatedVector4(encodeNative(associatedScaled))); + } + + private static Vector4 Associate(Vector4 vector) + { + vector.X *= vector.W; + vector.Y *= vector.W; + vector.Z *= vector.W; + return vector; + } +} + +/// +/// Provides exact floating-point bounds for independently ordered alpha-association calculations. +/// +internal static class AlphaRepresentationTestAssertions +{ + /// + /// Verifies that corresponding components differ by no more than one binary32 value. + /// + /// The independently calculated expected vector. + /// The vector produced by the pixel implementation. + internal static void EqualWithinOneUlp(Vector4 expected, Vector4 actual) + { + // One neighboring value is the strict bound for a single independently ordered, correctly rounded binary32 operation. + Assert.InRange(actual.X, MathF.BitDecrement(expected.X), MathF.BitIncrement(expected.X)); + Assert.InRange(actual.Y, MathF.BitDecrement(expected.Y), MathF.BitIncrement(expected.Y)); + Assert.InRange(actual.Z, MathF.BitDecrement(expected.Z), MathF.BitIncrement(expected.Z)); + Assert.InRange(actual.W, MathF.BitDecrement(expected.W), MathF.BitIncrement(expected.W)); + } + + /// + /// Verifies that corresponding components differ by no more than two binary32 values. + /// + /// The independently calculated expected vector. + /// The vector produced by the pixel implementation. + internal static void EqualWithinTwoUlps(Vector4 expected, Vector4 actual) + { + // Two neighboring values are the exhaustive bound for NormalizedByte4P unassociation followed by reassociation. + // Bit increments express that exact bound without introducing a loose decimal tolerance. + Assert.InRange(actual.X, MathF.BitDecrement(MathF.BitDecrement(expected.X)), MathF.BitIncrement(MathF.BitIncrement(expected.X))); + Assert.InRange(actual.Y, MathF.BitDecrement(MathF.BitDecrement(expected.Y)), MathF.BitIncrement(MathF.BitIncrement(expected.Y))); + Assert.InRange(actual.Z, MathF.BitDecrement(MathF.BitDecrement(expected.Z)), MathF.BitIncrement(MathF.BitIncrement(expected.Z))); + Assert.InRange(actual.W, MathF.BitDecrement(MathF.BitDecrement(expected.W)), MathF.BitIncrement(MathF.BitIncrement(expected.W))); + } +} + +public class A8AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Abgr32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Abgr32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Argb32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Argb32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgr24AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgr565AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra4444AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra5551AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Byte4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfSingleAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfVector2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfVector4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfVector4PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class L16AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class L8AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class La16AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class La32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedByte2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedByte4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedByte4PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedShort2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedShort4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rg32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgb24AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgb48AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgb96AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba1010102AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba128AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaHalfAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaHalfPAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba64AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaVectorAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Short2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Short4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaDoubleAlphaRepresentationTests : PixelAlphaRepresentationTests { } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 36ddad6c64..a851e2fcf5 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -222,6 +222,27 @@ public void BlendFunctionsAreCalledForAllModeCombinations() => 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() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunAssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void RunAssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero() + { + Rgba32P background = Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)); + Rgba32P source = Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)); + PixelBlender blender = PixelOperations.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 +645,167 @@ private static void ExerciseAllBlenderModeCombinations() } } + 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()) + { + foreach (PixelColorBlendingMode colorMode in Enum.GetValues()) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode); + AssertAssociatedBlenderMatchesScalar(blender, background, source, colorMode, alphaMode); + } + } + } + + private static void AssertAssociatedBlenderMatchesScalar( + PixelBlender 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(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(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(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(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 blender, Rgba32P background, Rgba32P source, float amount, float coverage) + { + Span destination = stackalloc Rgba32P[1]; + Span backgroundSpan = stackalloc Rgba32P[1] { background }; + Span sourceSpan = stackalloc Rgba32P[1] { source }; + Span coverageSpan = stackalloc float[1] { coverage }; + Span buffer = stackalloc Vector4[3]; + + // A one-pixel span takes the scalar remainder path, providing an exact oracle for each SIMD coverage result. + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer); + return destination[0]; + } + + private static void AssertRgba32PEqual( + ReadOnlySpan expected, + ReadOnlySpan 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 blender) { Rgba32 background = Color.MistyRose.ToPixel(); Rgba32 source = Color.MidnightBlue.ToPixel(); + + ExerciseBlender(blender, background, source); + } + + private static void ExerciseBlender(PixelBlender blender, TPixel background, TPixel source) + where TPixel : unmanaged, IPixel + { 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(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, sourceSpanBuffer); + blender.Blend(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 +817,7 @@ private static void ExerciseBlender(PixelBlender blender) blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer); Assert.All(destination, x => Assert.Equal(expected, x)); - blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, coverage, sourceSpanBuffer); + blender.BlendWithCoverage(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); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 2a5c5765ab..dbff541407 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/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 static byte[] MakeAbgr32ByteArray(byte r, byte g, byte b, byte a) } internal static void To( - Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) where TSourcePixel : unmanaged, IPixel where TDestinationPixel : unmanaged, IPixel { - Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); int count = sourcePixels.Length; @@ -92,13 +91,14 @@ internal static void To( 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 = sp.ToUnassociatedScaledVector4(); + dp = TDestinationPixel.FromUnassociatedScaledVector4(vector); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs index b372829270..3747b41eeb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ b/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. // @@ -7,7 +7,6 @@ using Xunit; using Xunit.Abstractions; - namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; public partial class PixelOperationsTests @@ -43,6 +42,21 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class Argb32P_OperationsTests : PixelOperationsTests + { + 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 { public Abgr32_OperationsTests(ITestOutputHelper output) @@ -58,6 +72,21 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class Abgr32P_OperationsTests : PixelOperationsTests + { + 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 { public Bgr24_OperationsTests(ITestOutputHelper output) @@ -103,6 +132,21 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class Bgra32P_OperationsTests : PixelOperationsTests + { + 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 { public Bgra4444_OperationsTests(ITestOutputHelper output) @@ -193,6 +237,21 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class HalfVector4P_OperationsTests : PixelOperationsTests + { + 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 { public L16_OperationsTests(ITestOutputHelper output) @@ -283,6 +342,21 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class NormalizedByte4P_OperationsTests : PixelOperationsTests + { + 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 { public NormalizedShort2_OperationsTests(ITestOutputHelper output) @@ -388,6 +462,21 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class Rgba32P_OperationsTests : PixelOperationsTests + { + 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 { public Rgba64_OperationsTests(ITestOutputHelper output) @@ -403,6 +492,36 @@ public void PixelTypeInfoHasCorrectAlphaRepresentation() } } + public partial class RgbaHalf_OperationsTests : PixelOperationsTests + { + public RgbaHalf_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = RgbaHalf.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); + } + } + + public partial class RgbaHalfP_OperationsTests : PixelOperationsTests + { + public RgbaHalfP_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = RgbaHalfP.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class RgbaVector_OperationsTests : PixelOperationsTests { public RgbaVector_OperationsTests(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude index ccf90fe40f..e4a7e881e6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -29,32 +29,47 @@ using Xunit.Abstractions; "Rgba1010102", "Rgba32", "Rgba64", + "RgbaHalf", "RgbaVector", "Short4" ]; - private static readonly string[] AssociatedAlphaPixelTypes = []; + private static readonly string[] AssociatedAlphaPixelTypes = + [ + "Argb32P", + "Abgr32P", + "Bgra32P", + "HalfVector4P", + "NormalizedByte4P", + "Rgba32P", + "RgbaHalfP" + ]; 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,7 +77,10 @@ using Xunit.Abstractions; "Rgb48", "Rgba1010102", "Rgba32", + "Rgba32P", "Rgba64", + "RgbaHalf", + "RgbaHalfP", "RgbaVector", "Short2", "Short4" diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 32b62fc03d..c569919f5f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -70,7 +70,7 @@ protected PixelOperationsTests(ITestOutputHelper output) protected virtual PixelOperations Operations { get; } = PixelOperations.Instance; - protected bool HasUnassociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; + protected bool HasAssociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated; internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) { @@ -102,6 +102,35 @@ internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefActi return expected; } + /// + /// Creates the scalar oracle for companded scaled input. + /// + /// The source vectors. + /// Whether the source vectors use associated alpha. + /// The converted pixels. + internal static TPixel[] CreateCompandedExpectedPixelData(Vector4[] source, bool associated) + { + TPixel[] expected = new TPixel[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + Vector4 vector = source[i]; + + if (associated) + { + Numerics.UnPremultiply(ref vector); + } + + vector = SRgbCompanding.Compress(vector); + + // Transfer functions operate on straight RGB. Let the destination pixel perform any required association so its stored + // alpha quantization participates exactly once, matching the public bulk conversion contract. + expected[i] = TPixel.FromUnassociatedScaledVector4(vector); + } + + return expected; + } + [Fact] public void PixelTypeInfoHasCorrectBitsPerPixel() { @@ -161,12 +190,18 @@ public void FromScaledVector4(int count) [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void SourceAction(ref Vector4 v) => v = SRgbCompanding.Expand(v); + void SourceAction(ref Vector4 v) + { + v = SRgbCompanding.Expand(v); - void ExpectedAction(ref Vector4 v) => v = SRgbCompanding.Compress(v); + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } Vector4[] source = CreateVector4TestData(count, SourceAction); - TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); + TPixel[] expected = CreateCompandedExpectedPixelData(source, this.HasAssociatedAlpha); TestOperation( source, @@ -183,35 +218,27 @@ public void FromCompandedScaledVector4(int count) [MemberData(nameof(ArraySizesData))] public void FromPremultipliedVector4(int count) { - void SourceAction(ref Vector4 v) - { - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } - } + Vector4[] source = CreateVector4TestData(count); + TPixel[] expected = new TPixel[count]; - void ExpectedAction(ref Vector4 v) + for (int i = 0; i < source.Length; i++) { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } + // Native W is not necessarily opacity. Use the per-pixel representation contract to create and consume associated-native vectors. + TPixel pixel = TPixel.FromUnassociatedVector4(source[i]); + source[i] = pixel.ToAssociatedVector4(); + expected[i] = TPixel.FromAssociatedVector4(source[i]); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); - TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); - TestOperation( source, 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 +248,7 @@ public void FromPremultipliedScaledVector4(int count) { void SourceAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.Premultiply(ref v); } @@ -229,7 +256,7 @@ void SourceAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.UnPremultiply(ref v); } @@ -243,15 +270,11 @@ void ExpectedAction(ref Vector4 v) 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); }); } @@ -262,40 +285,22 @@ public void FromCompandedPremultipliedScaledVector4(int count) void SourceAction(ref Vector4 v) { v = SRgbCompanding.Expand(v); - - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } - } - - void ExpectedAction(ref Vector4 v) - { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } - - v = SRgbCompanding.Compress(v); + Numerics.Premultiply(ref v); } Vector4[] source = CreateVector4TestData(count, SourceAction); - TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); + TPixel[] expected = CreateCompandedExpectedPixelData(source, true); TestOperation( source, 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 +322,27 @@ public void ToVector4(int count) { new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), @@ -340,7 +350,10 @@ public void ToVector4(int count) new TestPixel(), new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), + new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), @@ -355,7 +368,7 @@ public void Generic_To(TestPixel _) TPixel[] source = CreatePixelTestData(count); TDestPixel[] expected = new TDestPixel[count]; - PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); + PixelConverterTests.ReferenceImplementations.To(source, expected); TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false); } @@ -383,9 +396,26 @@ public void ToCompandedScaledVector4(int count) { void SourceAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } } - void ExpectedAction(ref Vector4 v) => v = SRgbCompanding.Expand(v); + void ExpectedAction(ref Vector4 v) + { + if (this.HasAssociatedAlpha) + { + Numerics.UnPremultiply(ref v); + } + + v = SRgbCompanding.Expand(v); + + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } TPixel[] source = CreateScaledPixelTestData(count, SourceAction); Vector4[] expected = CreateExpectedScaledVector4Data(source, ExpectedAction); @@ -404,15 +434,15 @@ void SourceAction(ref Vector4 v) [MemberData(nameof(ArraySizesData))] public void ToPremultipliedVector4(int count) { - void SourceAction(ref Vector4 v) + TPixel[] source = CreatePixelTestData(count); + Vector4[] expected = new Vector4[count]; + + for (int i = 0; i < source.Length; i++) { + // Association is defined by the pixel format because native W can be affine or integer-valued rather than normalized opacity. + expected[i] = source[i].ToAssociatedVector4(); } - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); - - TPixel[] source = CreatePixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedVector4Data(source, ExpectedAction); - TestOperation( source, expected, @@ -427,7 +457,13 @@ void SourceAction(ref Vector4 v) { } - 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)); @@ -448,10 +484,19 @@ public void ToCompandedPremultipliedScaledVector4(int count) { void SourceAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } } void ExpectedAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.UnPremultiply(ref v); + } + v = SRgbCompanding.Expand(v); Numerics.Premultiply(ref v); } @@ -499,7 +544,7 @@ public void ToArgb32Bytes(int count) 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 +588,7 @@ public void ToBgr24Bytes(int count) 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 +630,7 @@ public void ToBgra32Bytes(int count) 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 +673,7 @@ public void ToAbgr32Bytes(int count) 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 +719,7 @@ public void ToBgra5551Bytes(int count) 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(ref bgra); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -714,7 +759,7 @@ public void ToL8(int count) for (int i = 0; i < count; i++) { - expected[i] = L8.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = L8.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); } TestOperation( @@ -751,7 +796,7 @@ public void ToL16(int count) for (int i = 0; i < count; i++) { - expected[i] = L16.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = L16.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); } TestOperation( @@ -793,7 +838,7 @@ public void ToLa16Bytes(int count) 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(ref la); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -838,7 +883,7 @@ public void ToLa32Bytes(int count) 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(ref la); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -882,7 +927,7 @@ public void ToRgb24Bytes(int count) 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 +969,7 @@ public void ToRgba32Bytes(int count) 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 +1012,7 @@ public void ToRgb48Bytes(int count) 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(ref rgb); expected[i6] = rgb48Bytes[0]; expected[i6 + 1] = rgb48Bytes[1]; @@ -1013,7 +1058,7 @@ public void ToRgba64Bytes(int count) 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(ref rgba); expected[i8] = rgba64Bytes[0]; expected[i8 + 1] = rgba64Bytes[1]; @@ -1072,6 +1117,9 @@ internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAc return expected; } + // The scalar conversion boundary owns representation-specific rounding. Byte-export tests compare each bulk operation with that scalar result. + private static Vector4 ToUnassociatedScaledVector4(TPixel source) => source.ToUnassociatedScaledVector4(); + internal static void TestOperation( TSource[] source, TDest[] expected, @@ -1208,15 +1256,34 @@ public void Verify() 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 expected = this.ExpectedDestBuffer.AsSpan(); Span 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() <= 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 diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs new file mode 100644 index 0000000000..9b22a9f8c8 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs @@ -0,0 +1,283 @@ +// 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.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Tests the unit-range binary16 RGBA pixel formats. +/// +[Trait("Category", "PixelFormats")] +public class RgbaHalfTests +{ + /// + /// Verifies that the unassociated format has the native layout required by RGBA binary16 surfaces. + /// + [Fact] + public void RgbaHalfHasRgbaBinary16Layout() + { + RgbaHalf pixel = new(.25F, .5F, .75F, 1F); + ulong expected = BitConverter.HalfToUInt16Bits((Half).25F) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).5F) << 16) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).75F) << 32) + | ((ulong)BitConverter.HalfToUInt16Bits((Half)1F) << 48); + + Assert.Equal(8, Unsafe.SizeOf()); + Assert.Equal(expected, pixel.PackedValue); + Assert.Equal(new Vector4(.25F, .5F, .75F, 1F), pixel.ToVector4()); + Assert.Equal(pixel.ToVector4(), pixel.ToScaledVector4()); + } + + /// + /// Verifies that zero-filled binary16 storage represents transparent black without affine remapping. + /// + [Fact] + public void RgbaHalfDefaultIsTransparentBlack() + { + RgbaHalf pixel = default; + + Assert.Equal(Vector4.Zero, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + } + + /// + /// Verifies that scaled input is clamped to the pixel format's unit color range. + /// + [Fact] + public void RgbaHalfFromScaledVector4ClampsToUnitRange() + { + RgbaHalf pixel = RgbaHalf.FromScaledVector4(new Vector4(-1F, .5F, 2F, 1F)); + + Assert.Equal(new Vector4(0F, .5F, 1F, 1F), pixel.ToScaledVector4()); + } + + /// + /// Verifies that the associated format stores associated binary16 components in the same RGBA order. + /// + [Fact] + public void RgbaHalfPHasAssociatedRgbaBinary16Layout() + { + RgbaHalfP pixel = new(.125F, .25F, .375F, .5F); + ulong expected = BitConverter.HalfToUInt16Bits((Half).125F) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).25F) << 16) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).375F) << 32) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).5F) << 48); + + Assert.Equal(8, Unsafe.SizeOf()); + Assert.Equal(expected, pixel.PackedValue); + Assert.Equal(new Vector4(.125F, .25F, .375F, .5F), pixel.ToAssociatedScaledVector4()); + } + + /// + /// Verifies that zero-filled associated binary16 storage represents transparent black. + /// + [Fact] + public void RgbaHalfPDefaultIsTransparentBlack() + { + RgbaHalfP pixel = default; + + Assert.Equal(Vector4.Zero, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + Assert.Equal(Vector4.Zero, pixel.ToUnassociatedScaledVector4()); + } + + /// + /// Verifies that association uses the alpha value that survives binary16 quantization. + /// + [Fact] + public void RgbaHalfPQuantizesAlphaBeforeAssociation() + { + Vector4 source = new(.75F, .5F, .25F, 1F / 3F); + float storedAlpha = (float)(Half)source.W; + RgbaHalfP pixel = RgbaHalfP.FromUnassociatedScaledVector4(source); + + Assert.Equal((Half)(source.X * storedAlpha), pixel.R); + Assert.Equal((Half)(source.Y * storedAlpha), pixel.G); + Assert.Equal((Half)(source.Z * storedAlpha), pixel.B); + Assert.Equal((Half)storedAlpha, pixel.A); + } + + /// + /// Verifies every bulk representation path against its scalar pixel contract at each SIMD width and tail boundary. + /// + [Fact] + public void RgbaHalfBulkConversionsMatchScalarAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + static () => AssertBulkConversionsMatchScalar(), + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies every associated bulk representation path against its scalar pixel contract at each SIMD width and tail boundary. + /// + [Fact] + public void RgbaHalfPBulkConversionsMatchScalarAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + static () => AssertBulkConversionsMatchScalar(), + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies the declared component precision and alpha representation for the unassociated format. + /// + [Fact] + public void RgbaHalfPixelInformationIsCorrect() => AssertPixelInformation(PixelAlphaRepresentation.Unassociated); + + /// + /// Verifies the declared component precision and alpha representation for the associated format. + /// + [Fact] + public void RgbaHalfPPixelInformationIsCorrect() => AssertPixelInformation(PixelAlphaRepresentation.Associated); + + /// + /// Compares bulk conversions with the corresponding scalar pixel operations for representative vector widths and remainders. + /// + /// The binary16 pixel format to test. + private static void AssertBulkConversionsMatchScalar() + where TPixel : unmanaged, IPixel + { + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + PixelConversionModifiers[] modifiers = + [ + PixelConversionModifiers.None, + PixelConversionModifiers.Scale, + PixelConversionModifiers.Premultiply, + PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply, + PixelConversionModifiers.UnPremultiply, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply + ]; + + foreach (int length in lengths) + { + TPixel[] pixels = new TPixel[length]; + + for (int i = 0; i < length; i++) + { + pixels[i] = TPixel.FromUnassociatedScaledVector4(CreateUnassociatedVector(i)); + } + + foreach (PixelConversionModifiers modifier in modifiers) + { + Vector4[] expectedVectors = new Vector4[length]; + Vector4[] actualVectors = new Vector4[length]; + Vector4[] sourceVectors = new Vector4[length]; + TPixel[] expectedPixels = new TPixel[length]; + TPixel[] actualPixels = new TPixel[length]; + bool associated = RequestsAssociated(modifier); + bool scaled = modifier.IsDefined(PixelConversionModifiers.Scale); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = ToScalarVector(pixels[i], associated, scaled); + sourceVectors[i] = CreateUnassociatedVector(i + 19); + + if (associated) + { + Numerics.Premultiply(ref sourceVectors[i]); + } + + expectedPixels[i] = FromScalarVector(sourceVectors[i], associated, scaled); + } + + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualVectors, modifier); + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, sourceVectors.AsSpan(), actualPixels, modifier); + + Assert.Equal(expectedVectors, actualVectors); + Assert.Equal(expectedPixels, actualPixels); + } + } + } + + /// + /// Creates a deterministic unassociated unit-range vector for bulk conversion tests. + /// + /// The source index used to vary the component values. + /// The generated vector. + private static Vector4 CreateUnassociatedVector(int index) + => new( + ((index * 37) % 101) / 100F, + ((index * 53) % 101) / 100F, + ((index * 71) % 101) / 100F, + ((index * 89) % 101) / 100F); + + /// + /// Resolves whether a modifier combination requests associated output under the pixel format's native representation. + /// + /// The pixel format whose native representation participates in modifier resolution. + /// The conversion modifiers. + /// when the resulting vector representation is associated. + private static bool RequestsAssociated(PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + => modifiers.IsDefined(PixelConversionModifiers.Premultiply) + || (TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated + && !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply)); + + /// + /// Converts one pixel through the scalar API selected by the requested representation and range. + /// + /// The source pixel format. + /// The source pixel. + /// Whether the result should use associated alpha. + /// Whether the result should use the scaled range. + /// The converted vector. + private static Vector4 ToScalarVector(TPixel pixel, bool associated, bool scaled) + where TPixel : unmanaged, IPixel + => (associated, scaled) switch + { + (true, true) => pixel.ToAssociatedScaledVector4(), + (true, false) => pixel.ToAssociatedVector4(), + (false, true) => pixel.ToUnassociatedScaledVector4(), + _ => pixel.ToUnassociatedVector4() + }; + + /// + /// Converts one vector through the scalar API selected by its representation and range. + /// + /// The destination pixel format. + /// The source vector. + /// Whether the source uses associated alpha. + /// Whether the source uses the scaled range. + /// The converted pixel. + private static TPixel FromScalarVector(Vector4 vector, bool associated, bool scaled) + where TPixel : unmanaged, IPixel + => (associated, scaled) switch + { + (true, true) => TPixel.FromAssociatedScaledVector4(vector), + (true, false) => TPixel.FromAssociatedVector4(vector), + (false, true) => TPixel.FromUnassociatedScaledVector4(vector), + _ => TPixel.FromUnassociatedVector4(vector) + }; + + /// + /// Verifies the component layout and alpha metadata exposed by a binary16 pixel format. + /// + /// The pixel format to inspect. + /// The expected alpha representation. + private static void AssertPixelInformation(PixelAlphaRepresentation alphaRepresentation) + where TPixel : unmanaged, IPixel + { + PixelTypeInfo info = TPixel.GetPixelTypeInfo(); + PixelComponentInfo componentInfo = info.ComponentInfo.Value; + + Assert.Equal(64, info.BitsPerPixel); + Assert.Equal(alphaRepresentation, info.AlphaRepresentation); + Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); + Assert.Equal(4, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + + for (int i = 0; i < componentInfo.ComponentCount; i++) + { + Assert.Equal(16, componentInfo.GetComponentPrecision(i)); + } + } +} + +/// +/// Applies the shared associated-alpha contract to . +/// +public class RgbaHalfPAssociatedAlphaTests : AssociatedAlphaPixelTests +{ +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index f23da0c7a6..86fc828304 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -51,33 +52,44 @@ public void Short2_Clamping() [Fact] public void Short2_ToScaledVector4() { - // arrange - Short2 short2 = new(Vector2.One * 0x7FFF); - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(new Vector4(1F, 1F, 0F, 1F), new Short2(Vector2.One * short.MaxValue).ToScaledVector4()); + Assert.Equal(new Vector4(0F, 0F, 0F, 1F), new Short2(Vector2.One * short.MinValue).ToScaledVector4()); } [Fact] public void Short2_FromScaledVector4() { - // arrange - Short2 short2 = new(Vector2.One * 0x7FFF); - const ulong expected = 0x7FFF7FFF; + Assert.Equal(0x80008000U, Short2.FromScaledVector4(new Vector4(0F, 0F, 0F, 1F)).PackedValue); + Assert.Equal(0x7FFF7FFFU, Short2.FromScaledVector4(Vector4.One).PackedValue); + } - // act - Vector4 scaled = short2.ToScaledVector4(); - Short2 pixel = Short2.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; + [Fact] + public void Short2_BulkScaledConversionsCoverFullSignedRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertShort2BulkScaledConversionsCoverFullSignedRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - // assert - Assert.Equal(expected, actual); + private static void AssertShort2BulkScaledConversionsCoverFullSignedRange() + { + const int length = 17; + Short2[] source = new Short2[length]; + Vector4[] expectedVectors = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + bool minimum = (i & 1) == 0; + source[i].PackedValue = minimum ? 0x80008000U : 0x7FFF7FFFU; + expectedVectors[i] = minimum ? new Vector4(0F, 0F, 0F, 1F) : new Vector4(1F, 1F, 0F, 1F); + } + + Vector4[] actualVectors = new Vector4[length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + Short2[] actualPixels = new Short2[length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 819ff0e1e5..c8502462df 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -38,32 +39,44 @@ public void Short4_ToVector4() [Fact] public void Short4_ToScaledVector4() { - // arrange - Short4 short4 = new(Vector4.One * 0x7FFF); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(Vector4.One, new Short4(Vector4.One * short.MaxValue).ToScaledVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.One * short.MinValue).ToScaledVector4()); } [Fact] public void Short4_FromScaledVector4() { - // arrange - Short4 short4 = new(Vector4.One * 0x7FFF); - Vector4 scaled = short4.ToScaledVector4(); - const long expected = 0x7FFF7FFF7FFF7FFF; + Assert.Equal(0x8000800080008000UL, Short4.FromScaledVector4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, Short4.FromScaledVector4(Vector4.One).PackedValue); + } - // act - Short4 pixel = Short4.FromScaledVector4(scaled); + [Fact] + public void Short4_BulkScaledConversionsCoverFullSignedRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertShort4BulkScaledConversionsCoverFullSignedRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - // assert - Assert.Equal((ulong)expected, pixel.PackedValue); + private static void AssertShort4BulkScaledConversionsCoverFullSignedRange() + { + const int length = 17; + Short4[] source = new Short4[length]; + Vector4[] expectedVectors = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + bool minimum = (i & 1) == 0; + source[i].PackedValue = minimum ? 0x8000800080008000UL : 0x7FFF7FFF7FFF7FFFUL; + expectedVectors[i] = minimum ? Vector4.Zero : Vector4.One; + } + + Vector4[] actualVectors = new Vector4[length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + Short4[] actualPixels = new Short4[length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); } [Fact] diff --git a/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs b/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs new file mode 100644 index 0000000000..485f641748 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs @@ -0,0 +1,441 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Extensions.Convolution; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.Processing.Processors.Normalization; + +namespace SixLabors.ImageSharp.Tests.Processing; + +[Trait("Category", "Processors")] +public class AlphaAssociationProcessorTests +{ + [Theory] + [InlineData(ProcessorCase.Opaque)] + [InlineData(ProcessorCase.Invert)] + [InlineData(ProcessorCase.ColorMatrix)] + [InlineData(ProcessorCase.Convolution)] + [InlineData(ProcessorCase.ConvolutionPreserveAlpha)] + [InlineData(ProcessorCase.Convolution2D)] + [InlineData(ProcessorCase.Convolution2DPreserveAlpha)] + [InlineData(ProcessorCase.Convolution2Pass)] + [InlineData(ProcessorCase.Convolution2PassPreserveAlpha)] + [InlineData(ProcessorCase.Median)] + [InlineData(ProcessorCase.MedianPreserveAlpha)] + [InlineData(ProcessorCase.BokehBlur)] + [InlineData(ProcessorCase.OilPaint)] + [InlineData(ProcessorCase.HistogramGlobal)] + [InlineData(ProcessorCase.HistogramAdaptiveSlidingWindow)] + [InlineData(ProcessorCase.HistogramAdaptiveTileInterpolation)] + [InlineData(ProcessorCase.HistogramAutoLevel)] + [InlineData(ProcessorCase.HistogramAutoLevelSeparateChannels)] + [InlineData(ProcessorCase.ResizeCompanded)] + [InlineData(ProcessorCase.ResizeUnassociated)] + [InlineData(ProcessorCase.AffineTransform)] + [InlineData(ProcessorCase.ProjectiveTransform)] + [InlineData(ProcessorCase.OrderedDither)] + [InlineData(ProcessorCase.EntropyCrop)] + public void EquivalentAlphaRepresentationsProduceEquivalentResults(ProcessorCase processor) + { + using Image unassociated = processor == ProcessorCase.EntropyCrop ? CreateEntropyCropImage() : CreateTestImage(); + using Image associated = CreateAssociatedImage(unassociated); + + ApplyProcessor(unassociated, processor); + ApplyProcessor(associated, processor); + + AssertEquivalent(unassociated, associated); + } + + /// + /// Verifies the native-range bulk hooks required by the public associated-alpha operation contract. + /// + [Fact] + public void ScaledAssociatedPixelNativeBulkConversionsMatchScalarConversions() + { + const int length = 259; + ScaledRgbaVectorP pixel = ScaledRgbaVectorP.FromUnassociatedScaledVector4(new Vector4(.8F, .4F, .2F, .5F)); + ScaledRgbaVectorP[] pixels = new ScaledRgbaVectorP[length]; + Vector4[] expectedVectors = new Vector4[length]; + Vector4[] actualVectors = new Vector4[length]; + ScaledRgbaVectorP[] expectedPixels = new ScaledRgbaVectorP[length]; + ScaledRgbaVectorP[] actualPixels = new ScaledRgbaVectorP[length]; + PixelOperations operations = PixelOperations.Instance; + + Array.Fill(pixels, pixel); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToUnassociatedVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualVectors, PixelConversionModifiers.UnPremultiply); + Assert.Equal(expectedVectors, actualVectors); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToAssociatedVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualVectors, PixelConversionModifiers.Premultiply); + Assert.Equal(expectedVectors, actualVectors); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToUnassociatedVector4(); + expectedPixels[i] = ScaledRgbaVectorP.FromUnassociatedVector4(expectedVectors[i]); + } + + operations.FromVector4Destructive(Configuration.Default, expectedVectors, actualPixels, PixelConversionModifiers.UnPremultiply); + Assert.Equal(expectedPixels, actualPixels); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToAssociatedVector4(); + expectedPixels[i] = ScaledRgbaVectorP.FromAssociatedVector4(expectedVectors[i]); + } + + operations.FromVector4Destructive(Configuration.Default, expectedVectors, actualPixels, PixelConversionModifiers.Premultiply); + Assert.Equal(expectedPixels, actualPixels); + } + + [Fact] + public void ErrorDiffusionUsesLogicalUnassociatedError() + { + Vector4 background = new(.25F, .25F, .25F, .5F); + using Image unassociated = new(4, 4, RgbaVector.FromScaledVector4(background)); + using Image associated = CreateAssociatedImage(unassociated); + + Vector4 source = new(.5F, .5F, .5F, .5F); + Vector4 transformed = new(.25F, .25F, .25F, .5F); + RgbaVector unassociatedSource = RgbaVector.FromScaledVector4(source); + RgbaVector unassociatedTransformed = RgbaVector.FromScaledVector4(transformed); + ScaledRgbaVectorP associatedSource = ScaledRgbaVectorP.FromUnassociatedScaledVector4(source); + ScaledRgbaVectorP associatedTransformed = ScaledRgbaVectorP.FromUnassociatedScaledVector4(transformed); + + ErrorDither.FloydSteinberg.Dither(unassociated.Frames.RootFrame, unassociated.Bounds, unassociatedSource, unassociatedTransformed, 1, 1, 1F); + ErrorDither.FloydSteinberg.Dither(associated.Frames.RootFrame, associated.Bounds, associatedSource, associatedTransformed, 1, 1, 1F); + + AssertEquivalent(unassociated, associated); + } + + [Fact] + public void AffineTransformFractionalEdgesMatchNormalPixelConversion() + { + using Image rgba = new(4, 4, new Rgba32(255, 64, 16, 255)); + using Image bgra = rgba.CloneAs(); + using Image rgb = rgba.CloneAs(); + + ApplyProcessor(rgba, ProcessorCase.AffineTransform); + ApplyProcessor(bgra, ProcessorCase.AffineTransform); + ApplyProcessor(rgb, ProcessorCase.AffineTransform); + + // Alpha-less output drops fractional coverage after unassociation, just like a normal pixel-format conversion. + using Image expectedBgra = rgba.CloneAs(); + using Image expectedRgb = rgba.CloneAs(); + + Assert.Equal(rgba.Size, bgra.Size); + Assert.Equal(rgba.Size, rgb.Size); + + bool foundFractionalCoverage = false; + for (int y = 0; y < rgba.Height; y++) + { + for (int x = 0; x < rgba.Width; x++) + { + Rgba32 pixel = rgba[x, y]; + + if (pixel.A is > 0 and < 255) + { + foundFractionalCoverage = true; + Assert.Equal(new Rgba32(255, 64, 16, pixel.A), pixel); + } + + Assert.Equal(expectedBgra[x, y], bgra[x, y]); + Assert.Equal(expectedRgb[x, y], rgb[x, y]); + } + } + + Assert.True(foundFractionalCoverage); + } + + [Fact] + public void ConvolutionDoesNotObserveColorBehindZeroAlpha() + { + using Image hiddenColor = new(3, 1); + using Image transparentBlack = new(3, 1); + + hiddenColor[0, 0] = new RgbaVector(1, 0, 0, 0); + hiddenColor[1, 0] = new RgbaVector(0, 0, 1, 1); + hiddenColor[2, 0] = new RgbaVector(1, 0, 0, 0); + transparentBlack[1, 0] = hiddenColor[1, 0]; + + ApplyProcessor(hiddenColor, ProcessorCase.Convolution); + ApplyProcessor(transparentBlack, ProcessorCase.Convolution); + + // Associated filtering makes fully transparent RGB unobservable before the kernel is evaluated. + for (int x = 0; x < hiddenColor.Width; x++) + { + Assert.Equal(transparentBlack[x, 0], hiddenColor[x, 0]); + } + } + + private static Image CreateTestImage() + { + Image image = new(8, 8); + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + float alpha = ((x + (2 * y)) % 3) switch + { + 0 => .25F, + 1 => .5F, + _ => 1F + }; + + // Dyadic components and alpha make the initial association round trip exact in binary floating point. + float red = ((x + y) & 7) / 8F; + float green = (((3 * x) + y + 1) & 7) / 8F; + float blue = ((x + (5 * y) + 2) & 7) / 8F; + image[x, y] = new RgbaVector(red, green, blue, alpha); + } + } + + return image; + } + + private static Image CreateEntropyCropImage() + { + Image image = new(8, 8, new RgbaVector(0, 0, 0, 1)); + + for (int y = 2; y < 6; y++) + { + for (int x = 2; x < 6; x++) + { + image[x, y] = new RgbaVector(1, 1, 1, 1); + } + } + + return image; + } + + private static Image CreateAssociatedImage(Image source) + { + Image image = new(source.Width, source.Height); + + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < source.Width; x++) + { + Vector4 vector = source[x, y].ToScaledVector4(); + Numerics.Premultiply(ref vector); + image[x, y] = ScaledRgbaVectorP.FromScaledVector4(vector); + } + } + + return image; + } + + private static void ApplyProcessor(Image image, ProcessorCase processor) + where TPixel : unmanaged, IPixel + { + if (processor == ProcessorCase.Opaque) + { + using OpaqueProcessor opaque = new(image.Configuration, image, image.Bounds); + opaque.Apply(image.Frames.RootFrame); + return; + } + + if (processor is ProcessorCase.Convolution2D or ProcessorCase.Convolution2DPreserveAlpha) + { + DenseMatrix kernelX = new float[,] + { + { -.25F, 0, .25F }, + { -.5F, 0, .5F }, + { -.25F, 0, .25F } + }; + DenseMatrix kernelY = new float[,] + { + { -.25F, -.5F, -.25F }, + { 0, 0, 0 }, + { .25F, .5F, .25F } + }; + + using Convolution2DProcessor convolution = new( + image.Configuration, + kernelX, + kernelY, + processor == ProcessorCase.Convolution2DPreserveAlpha, + image, + image.Bounds); + + convolution.Apply(image.Frames.RootFrame); + return; + } + + if (processor is ProcessorCase.Convolution2Pass or ProcessorCase.Convolution2PassPreserveAlpha) + { + float[] kernel = [.25F, .5F, .25F]; + using Convolution2PassProcessor convolution = new( + image.Configuration, + kernel, + processor == ProcessorCase.Convolution2PassPreserveAlpha, + image, + image.Bounds, + BorderWrappingMode.Repeat, + BorderWrappingMode.Repeat); + + convolution.Apply(image.Frames.RootFrame); + return; + } + + image.Mutate(context => + { + switch (processor) + { + case ProcessorCase.Invert: + context.Invert(); + break; + case ProcessorCase.ColorMatrix: + ColorMatrix matrix = new() + { + M11 = .5F, + M22 = .5F, + M33 = .5F, + M41 = .25F, + M42 = .25F, + M43 = .25F, + M14 = .125F, + M24 = .125F, + M34 = .125F, + M44 = .5F + }; + + context.Filter(matrix); + break; + case ProcessorCase.Convolution: + context.Convolve(new float[,] { { .25F, .5F, .25F } }); + break; + case ProcessorCase.ConvolutionPreserveAlpha: + context.Convolve(new float[,] { { .25F, .5F, .25F } }, true); + break; + case ProcessorCase.Median: + context.MedianBlur(1, false); + break; + case ProcessorCase.MedianPreserveAlpha: + context.MedianBlur(1, true); + break; + case ProcessorCase.BokehBlur: + context.BokehBlur(2, 2, 2F); + break; + case ProcessorCase.OilPaint: + context.OilPaint(4, 3); + break; + case ProcessorCase.HistogramGlobal: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.Global)); + break; + case ProcessorCase.HistogramAdaptiveSlidingWindow: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.AdaptiveSlidingWindow)); + break; + case ProcessorCase.HistogramAdaptiveTileInterpolation: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.AdaptiveTileInterpolation)); + break; + case ProcessorCase.HistogramAutoLevel: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.AutoLevel)); + break; + case ProcessorCase.HistogramAutoLevelSeparateChannels: + HistogramEqualizationOptions options = CreateHistogramOptions(HistogramEqualizationMethod.AutoLevel); + options.SyncChannels = false; + context.HistogramEqualization(options); + break; + case ProcessorCase.ResizeCompanded: + context.Resize(new ResizeOptions + { + Size = new Size(5, 5), + Sampler = KnownResamplers.Box, + Compand = true, + PremultiplyAlpha = true + }); + break; + case ProcessorCase.ResizeUnassociated: + context.Resize(new ResizeOptions + { + Size = new Size(5, 5), + Sampler = KnownResamplers.Box, + PremultiplyAlpha = false + }); + break; + case ProcessorCase.AffineTransform: + context.Transform(image.Bounds, Matrix3x2.CreateTranslation(.5F, .5F), image.Size, KnownResamplers.Bicubic); + break; + case ProcessorCase.ProjectiveTransform: + context.Transform(image.Bounds, Matrix4x4.CreateTranslation(.5F, .5F, 0), image.Size, KnownResamplers.Bicubic); + break; + case ProcessorCase.OrderedDither: + context.Dither(KnownDitherings.Bayer4x4); + break; + case ProcessorCase.EntropyCrop: + context.EntropyCrop(.5F); + break; + } + }); + } + + private static HistogramEqualizationOptions CreateHistogramOptions(HistogramEqualizationMethod method) + => new() + { + Method = method, + LuminanceLevels = 16, + NumberOfTiles = 2 + }; + + private static void AssertEquivalent(Image unassociated, Image associated) + { + // Materialize both representations through the same 16-bit logical-color boundary. Their floating-point storage paths can differ by one rounding operation when associated results are unassociated for RgbaVector storage. + using Image expected = unassociated.CloneAs(); + using Image actual = associated.CloneAs(); + + Assert.Equal(expected.Size, actual.Size); + + for (int y = 0; y < expected.Height; y++) + { + for (int x = 0; x < expected.Width; x++) + { + Assert.Equal(expected[x, y], actual[x, y]); + } + } + } + + public enum ProcessorCase + { + Opaque, + Invert, + ColorMatrix, + Convolution, + ConvolutionPreserveAlpha, + Convolution2D, + Convolution2DPreserveAlpha, + Convolution2Pass, + Convolution2PassPreserveAlpha, + Median, + MedianPreserveAlpha, + BokehBlur, + OilPaint, + HistogramGlobal, + HistogramAdaptiveSlidingWindow, + HistogramAdaptiveTileInterpolation, + HistogramAutoLevel, + HistogramAutoLevelSeparateChannels, + ResizeCompanded, + ResizeUnassociated, + AffineTransform, + ProjectiveTransform, + OrderedDither, + EntropyCrop + } +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index 1352124004..d5f7f69d99 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -43,13 +43,14 @@ public void Brightness_scaled_vector() Assert.Equal(new Rgb24(20, 20, 20), rgbImage[0, 0]); - Image halfSingleImage = new(Configuration.Default, 100, 100, new HalfSingle(-1)); + // HalfSingle normalizes the complete finite binary16 interval, making -65504 logical zero and -32752 logical .25. + Image halfSingleImage = new(Configuration.Default, 100, 100, new HalfSingle((float)Half.MinValue)); halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - Assert.Equal(new HalfSingle(-1), halfSingleImage[0, 0]); + Assert.Equal(new HalfSingle((float)Half.MinValue), halfSingleImage[0, 0]); - halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-0.5f)); + halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-32752F)); halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs index 4ef2159305..b154551e24 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs @@ -59,4 +59,18 @@ public void HexadecatreeQuantizerCanCreateFrameQuantizer() Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); } + + [Fact] + public void PaletteUsesNormalizedTransparencyThreshold() + { + using HexadecatreeQuantizer.Hexadecatree tree = new(Configuration.Default, 8, 2, .5F); + tree.AddColors([new Rgba32(255, 255, 255, 127)]); + + Span palette = stackalloc Rgba32[2]; + short paletteIndex = 0; + tree.Palettize(palette, ref paletteIndex); + + Assert.Equal(1, paletteIndex); + Assert.Equal(default, palette[0]); + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 023d47886d..7389c4b898 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -228,6 +229,72 @@ public void Resize_PremultiplyAlpha(TestImageProvider provider, appendSourceFileOrDescription: false); } + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Resize_AppliesAlphaOptionsForUnassociatedPixels(bool compand, bool premultiplyAlpha) + => AssertResizeAlphaOptions(compand, premultiplyAlpha); + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Resize_AppliesAlphaOptionsForAssociatedPixels(bool compand, bool premultiplyAlpha) + => AssertResizeAlphaOptions(compand, premultiplyAlpha); + + private static void AssertResizeAlphaOptions(bool compand, bool premultiplyAlpha) + where TPixel : unmanaged, IPixel + { + using Image image = new(2, 1); + image[0, 0] = TPixel.FromUnassociatedScaledVector4(new Vector4(0.9F, 0.2F, 0.1F, 0.8F)); + image[1, 0] = TPixel.FromUnassociatedScaledVector4(new Vector4(0.1F, 0.8F, 0.6F, 0.2F)); + + // Read the stored pixels back through the scalar contract so the oracle includes the source format's actual quantization. + Vector4 expectedVector = image[0, 0].ToUnassociatedScaledVector4(); + Vector4 second = image[1, 0].ToUnassociatedScaledVector4(); + + if (compand) + { + expectedVector = SRgbCompanding.Expand(expectedVector); + second = SRgbCompanding.Expand(second); + } + + if (premultiplyAlpha) + { + Numerics.Premultiply(ref expectedVector); + Numerics.Premultiply(ref second); + } + + expectedVector = (expectedVector + second) / 2F; + + if (premultiplyAlpha) + { + Numerics.UnPremultiply(ref expectedVector); + } + + if (compand) + { + expectedVector = SRgbCompanding.Compress(expectedVector); + } + + TPixel expected = TPixel.FromUnassociatedScaledVector4(expectedVector); + ResizeOptions options = new() + { + Size = new Size(1, 1), + Mode = ResizeMode.Stretch, + Sampler = KnownResamplers.Box, + Compand = compand, + PremultiplyAlpha = premultiplyAlpha + }; + + image.Mutate(context => context.Resize(options)); + + Assert.Equal(expected, image[0, 0]); + } + [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs new file mode 100644 index 0000000000..ddad67b949 --- /dev/null +++ b/tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Tests.Quantization; + +public class AlphaAssociationQuantizerTests +{ + public static TheoryData Dithers { get; } = new() + { + null, + KnownDitherings.Bayer4x4, + KnownDitherings.FloydSteinberg + }; + + [Theory] + [MemberData(nameof(Dithers))] + public void WuQuantizerProducesEquivalentResultsForAssociatedPixels(IDither? dither) + { + byte[] alphaValues = [0, 1, 63, 64, 65, 127, 128, 254, 255]; + Rgba32[] source = new Rgba32[alphaValues.Length * 4]; + + for (int i = 0; i < source.Length; i++) + { + byte alpha = alphaValues[i % alphaValues.Length]; + source[i] = alpha == 0 ? default : new Rgba32(255, 255, 255, alpha); + } + + QuantizerOptions options = new() + { + Dither = dither, + MaxColors = 8, + TransparencyThreshold = .5F + }; + + Rgba32[] expected = Quantize(source, options); + Rgba32[] actual = Quantize(source, options); + + Assert.Equal(expected, actual); + } + + [Fact] + public void PaletteQuantizerAppliesTransparencyThresholdToAssociatedPixels() + { + Configuration configuration = Configuration.Default; + Rgba32P source = new(127, 0, 0, 127); + Color[] palette = [Color.Transparent, Color.FromPixel(source)]; + QuantizerOptions options = new() { Dither = null, TransparencyThreshold = .5F }; + PaletteQuantizer quantizer = new(palette, options); + + using Image image = new(configuration, 1, 1, source); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(configuration); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds); + + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); + Assert.Equal(default, result.Palette.Span[0]); + } + + [Theory] + [InlineData(64 / 255F, (byte)63, (byte)64)] + [InlineData(.5F, (byte)127, (byte)128)] + [InlineData(1F, (byte)254, (byte)255)] + public void WuQuantizerAppliesNormalizedTransparencyThresholdToAssociatedPixels(float threshold, byte below, byte retained) + { + Rgba32[] source = + [ + new Rgba32(255, 255, 255, below), + new Rgba32(255, 255, 255, retained) + ]; + + QuantizerOptions options = new() + { + Dither = null, + MaxColors = 2, + TransparencyThreshold = threshold + }; + + Rgba32[] expected = Quantize(source, options); + Rgba32[] actual = Quantize(source, options); + + Assert.Equal(expected, actual); + Assert.Equal(default, actual[0]); + Assert.NotEqual(0, actual[1].A); + } + + private static Rgba32[] Quantize(Rgba32[] source, QuantizerOptions options) + where TPixel : unmanaged, IPixel + { + Configuration configuration = Configuration.Default; + using Image image = new(configuration, source.Length, 1); + + for (int x = 0; x < source.Length; x++) + { + image[x, 0] = TPixel.FromRgba32(source[x]); + } + + WuQuantizer quantizer = new(options); + ImageFrame frame = image.Frames.RootFrame; + + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(configuration); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); + + ReadOnlySpan palette = result.Palette.Span; + ReadOnlySpan indices = result.DangerousGetRowSpan(0); + Rgba32[] colors = new Rgba32[source.Length]; + + for (int x = 0; x < colors.Length; x++) + { + colors[x] = palette[indices[x]].ToRgba32(); + } + + return colors; + } +} diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 28bfcfaa8d..a3de7b3feb 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -271,6 +271,18 @@ public struct TestPixelForAgnosticDecode : IPixel public readonly Vector4 ToVector4() => default; + /// + public readonly Vector4 ToUnassociatedScaledVector4() => default; + + /// + public readonly Vector4 ToAssociatedScaledVector4() => default; + + /// + public readonly Vector4 ToUnassociatedVector4() => default; + + /// + public readonly Vector4 ToAssociatedVector4() => default; + public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( PixelComponentInfo.Create(2, 8, 8), @@ -283,6 +295,18 @@ public static PixelTypeInfo GetPixelTypeInfo() public static TestPixelForAgnosticDecode FromVector4(Vector4 vector) => default; + /// + public static TestPixelForAgnosticDecode FromUnassociatedScaledVector4(Vector4 source) => default; + + /// + public static TestPixelForAgnosticDecode FromAssociatedScaledVector4(Vector4 source) => default; + + /// + public static TestPixelForAgnosticDecode FromUnassociatedVector4(Vector4 source) => default; + + /// + public static TestPixelForAgnosticDecode FromAssociatedVector4(Vector4 source) => default; + public static TestPixelForAgnosticDecode FromAbgr32(Abgr32 source) => default; public static TestPixelForAgnosticDecode FromArgb32(Argb32 source) => default; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 813ed505d8..ee1d274716 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -78,6 +78,6 @@ public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) return x < midX ? BottomLeftColor : BottomRightColor; } - private static TPixel GetBottomRightColor() => TPixel.FromScaledVector4(new Vector4(1f, 0f, 1f, 0.5f)); + private static TPixel GetBottomRightColor() => TPixel.FromUnassociatedScaledVector4(new Vector4(1f, 0f, 1f, 0.5f)); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 405d048fc5..12a7ad4e25 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -158,15 +158,17 @@ private static void TransparentGradients(Buffer2D pixels) int bottom = pixels.Height; int height = (int)Math.Ceiling(pixels.Height / 6f); - Vector4 red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - Vector4 green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - Vector4 blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + // Round-trip the primaries through TPixel so this fixture still demonstrates the format's color quantization, while + // keeping the working vectors unassociated because the loop replaces alpha independently of RGB. + Vector4 red = Color.Red.ToPixel().ToUnassociatedScaledVector4(); + Vector4 green = Color.Green.ToPixel().ToUnassociatedScaledVector4(); + Vector4 blue = Color.Blue.ToPixel().ToUnassociatedScaledVector4(); for (int x = left; x < right; x++) { blue.W = red.W = green.W = x / (float)right; - TPixel c = TPixel.FromVector4(red); + TPixel c = TPixel.FromUnassociatedScaledVector4(red); int topBand = top; for (int y = topBand; y < top + height; y++) { @@ -174,14 +176,14 @@ private static void TransparentGradients(Buffer2D pixels) } topBand += height; - c = TPixel.FromVector4(green); + c = TPixel.FromUnassociatedScaledVector4(green); for (int y = topBand; y < topBand + height; y++) { pixels[x, y] = c; } topBand += height; - c = TPixel.FromVector4(blue); + c = TPixel.FromUnassociatedScaledVector4(blue); for (int y = topBand; y < bottom; y++) { pixels[x, y] = c; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 73777ef60a..c7883fac15 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -278,7 +278,7 @@ public static void ModifyPixel(ImageFrame img, int x, int y, byt where TPixel : unmanaged, IPixel { TPixel pixel = img[x, y]; - Rgba64 rgbaPixel = Rgba64.FromScaledVector4(pixel.ToScaledVector4()); + Rgba64 rgbaPixel = Rgba64.FromUnassociatedScaledVector4(pixel.ToUnassociatedScaledVector4()); ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); if (rgbaPixel.R + perChannelChange <= 255) diff --git a/tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs b/tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs new file mode 100644 index 0000000000..aed4ef243d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs @@ -0,0 +1,271 @@ +// 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; + +namespace SixLabors.ImageSharp.Tests; + +/// +/// A test pixel format that stores associated scaled components as 32-bit floating-point values. +/// +/// +/// Its native vector range differs from its scaled range so processor tests also detect missing +/// conversion boundaries without introducing packed-pixel quantization. +/// +[StructLayout(LayoutKind.Sequential)] +internal struct ScaledRgbaVectorP : IPixel +{ + private Vector4 scaled; + + public ScaledRgbaVectorP(Vector4 scaled) => this.scaled = Numerics.Clamp(scaled, Vector4.Zero, Vector4.One); + + public readonly Rgba32 ToRgba32() + { + Vector4 unassociated = this.scaled; + Numerics.UnPremultiply(ref unassociated); + return Rgba32.FromScaledVector4(unassociated); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.scaled; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => (this.scaled * 2F) - Vector4.One; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + Vector4 vector = this.scaled; + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + Vector4 vector = this.ToUnassociatedScaledVector4(); + + // The native vector is an affine view of the scaled data, so association changes occur before mapping the result. + return (vector * 2F) - Vector4.One; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 32, 32, 32, 32), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + public static PixelOperations CreatePixelOperations() => new ScaledPixelOperations(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromScaledVector4(Vector4 source) => new(source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromVector4(Vector4 source) => new((source + Vector4.One) * .5F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromAssociatedScaledVector4(Vector4 source) + { + // Clamp logical color before restoring association so out-of-range alpha cannot permanently darken it. + Numerics.UnPremultiply(ref source); + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + Numerics.Premultiply(ref source); + return new(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromUnassociatedVector4(Vector4 source) + { + // The native vector is affine, so map it to scaled space before changing alpha representation. + source = (source + Vector4.One) * .5F; + return FromUnassociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromAssociatedVector4(Vector4 source) + { + // The native vector is affine, so map it to scaled space before changing alpha representation. + source = (source + Vector4.One) * .5F; + return FromAssociatedScaledVector4(source); + } + + public static ScaledRgbaVectorP FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public override readonly bool Equals(object obj) => obj is ScaledRgbaVectorP other && this.Equals(other); + + public readonly bool Equals(ScaledRgbaVectorP other) => this.scaled.Equals(other.scaled); + + public override readonly int GetHashCode() => this.scaled.GetHashCode(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromUnassociatedScaledVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + Numerics.Premultiply(ref source); + return new ScaledRgbaVectorP(source); + } + + /// + /// Provides representation-aware bulk operations for the floating-point test pixel. + /// + private sealed class ScaledPixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Span vectors = destination[..source.Length]; + + // The test pixel contains one Vector4 field, so the cast copies its associated storage without changing representation. + MemoryMarshal.Cast(source).CopyTo(vectors); + Numerics.UnPremultiply(vectors); + + // Association is defined in scaled space, so map the unassociated result to the test format's native [-1, 1] range afterwards. + for (int i = 0; i < vectors.Length; i++) + { + vectors[i] = (vectors[i] * 2F) - Vector4.One; + } + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Span vectors = destination[..source.Length]; + MemoryMarshal.Cast(source).CopyTo(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + vectors[i] = (vectors[i] * 2F) - Vector4.One; + } + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Span vectors = destination[..source.Length]; + MemoryMarshal.Cast(source).CopyTo(vectors); + Numerics.UnPremultiply(vectors); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + MapNativeToScaled(source); + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + MapNativeToScaled(source); + Numerics.UnPremultiply(source); + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // This test format stores floating-point alpha without quantization, so clamping and premultiplication can use the shared SIMD span operations directly. + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + + // Source now matches the pixel's sole associated Vector4 field and can be copied without a scalar conversion loop. + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Recovering the logical color before clamping preserves it when alpha lies outside the scaled range; premultiplication then restores the associated representation. + Numerics.UnPremultiply(source); + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + /// Maps vectors from the test format's native numeric range to its scaled range. + /// + /// The vectors to convert in place. + private static void MapNativeToScaled(Span vectors) + { + // Native values use [-1, 1], so the affine inverse maps them to [0, 1] before changing alpha representation. + for (int i = 0; i < vectors.Length; i++) + { + vectors[i] = (vectors[i] + Vector4.One) * .5F; + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index be2356d657..b51e4e2d44 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -826,14 +826,17 @@ public void Invoke(in RowInterval rows, Span span) for (int y = rows.Min; y < rows.Max; y++) { Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + for (int i = 0; i < span.Length; i++) { ref Vector4 v = ref span[i]; v.W = 1F; } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + // Changing opacity must operate on logical RGB. The representation-aware bulk conversion prevents associated + // formats from retaining the darker RGB values that belonged to the old alpha. + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index f0344e2b9c..174766dafb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -35,7 +35,7 @@ public TestPixel(float red, float green, float blue, float alpha) public float Alpha { get; set; } - public TPixel AsPixel() => TPixel.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + public TPixel AsPixel() => TPixel.FromUnassociatedScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); internal Span AsSpan() => new([this.AsPixel()]); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 46fb7159e8..714dbd223d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -31,7 +31,7 @@ public static Image CreateTestImage() Vector4 v = new(i, j, 0, 1); v /= 10; - pixels[i, j] = TPixel.FromVector4(v); + pixels[i, j] = TPixel.FromUnassociatedVector4(v); } } diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png index f705b63df8..96482b46e8 100644 --- a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:107c6d26cdb8aac539011acf7213376e930cb705f5815b010b31ce4b9e738f9b -size 25780 +oid sha256:46ef9cd2f212651a5ddc78aa1707df87c18ce227466b7b6f00d215f11271e61a +size 24590 diff --git a/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png index 4f57775dc0..aa561ace81 100644 --- a/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png +++ b/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65d3a099dd24fcee2fc86c63b8664ebd81d7b3c530168da5f4e50ee0ca925496 -size 759 +oid sha256:d76ae5ac1a0aebb9d76b2fa5c6b1fc109e5fcbdc6c41d9681a76ade9c86fab79 +size 748 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png index 87c1fb7286..2449c3efdb 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png index c697cd4c29..37323155f6 100644 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png index f3b7b0e809..8f63e3277e 100644 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png index 5b72c3732c..a3317a6b48 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3333503012aa29e23f0d0e52993ad3499514251208fb3318e2bb1560d54650fa -size 117956 +oid sha256:e50dfa103459d21642df5e1ca760081fbdfe3b7244624d9d87d8a20f45b51bbe +size 117953 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png index d2f11dc175..aded240a1e 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png index 412af0ac7a..2f546df5b5 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png index 35b99df985..4592d77da1 100644 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png +++ b/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